Enum equalization with other classes - dart

I've created a class that only takes Enums as parameters. I figured I could create a third Enum where I would manually put every option so they have a better naming.
The only thing is, I can't test if both my third Enum instance and my class instance with the same parameters are equal just by using the == operator. Tried using equatable and considering the Enum instance as my class instance since it does implement it, but nothing works. Of course, I could create a test where all my given parameters are equal, but I just wondered whether I could do something so they return true when using the == operator.
E.g.:
Configuration
enum A {
a,
b;
}
enum B {
c,
d;
}
class Class with EquatableMixin {
const EveryDayOfYear({required this.aValue, required this.bValue});
final A aValue;
final B bValue;
#override
List<Object?> get props => [aValue, bValue];
}
enum C {
ac(Class(aValue: A.a, bValue: B.c)),
ad(Class(aValue: A.a, bValue: B.d)),
bc(Class(aValue: A.b, bValue: B.c)),
bd(Class(aValue: A.b, bValue: B.d));
const C(this._handler);
final Class _handler;
#override
A get aValue => _handler.aValue;
#override
B get bValue => _handler.bValue;
#override
List<Object?> get props => [aValue, bValue];
}
Objective
final instance = Class(aValue: A.a, bValue: B.c);
instance == C.ac; // I would like something so this operation returns true.

As commented by #Levi-Lesches here the answer to my problem was to override my operator ==:
#override
// ignore: hash_and_equals, already implemented by EquatableMixin
bool operator ==(Object other) {
return (super == other) ||
((other is Class) &&
(week == other.aValue) &&
(day == other.bValue));
}
This solved my problem and is fair since my class instance is not an Enum, but my enum constant is actually my class instance.

Related

Use symbols to pass a class member's name?

I'm writing a testing utility function and would like to pass a class member name and get its value. Could I use Symbols for this? My test code looks like this
class Foo {
String a = 'A';
String b = 'B';
static void output(Symbol symbol) {
debugPrint("The value is '$symbol'");
}
}
Foo.output(#a);
I'm trying to get a result like The value is 'A' but I'm getting The value is 'Symbol("a")'?
Getting the value of something by name, where the name is a value, and at runtime, that is reflection.
You need dart:mirrors for that, which isn't available on most platforms.
Better yet, don't do it at all. Dart has first class functions, so you can pass in a function accessing the variable instead:
class Foo {
String a = 'A';
String b = 'B';
static void output(String read(Foo value)) {
debugPrint("The value is '${read(this)}'");
}
}
void main() {
var foo = Foo();
foo.output((f) => f.a);
foo.output((f) => f.b);
}

How to assert objects by its state (all properties values) in a Dart unit test?

I have a class (with more properties than the example, of course).
How to write the following simple test? I know that equality in Dart is by object instance. How to compare the full object state? I tested with the same matcher as well, with no luck.
import 'package:test/test.dart';
class UnderTesting {
var a;
var b;
UnderTesting({this.a, this.b});
}
void main() {
test("compare objects", () {
final obj1 = UnderTesting(a:1, b:2);
final obj2 = UnderTesting(a:1, b:2);
// Next will fail because it is checking if it is the same instance
expect(obj1, equals(obj2));
} );
}
You need to override the == operator (and should therefore also override hashCode) for UnderTesting. The documentation for equals tells us how to does test for equality:
If [expected] is a [Matcher], then it matches using that. Otherwise it tests for equality using == on the expected value.
So you code should be something like this:
import 'package:test/test.dart';
import 'package:quiver/core.dart';
class UnderTesting {
int a;
int b;
UnderTesting({this.a, this.b});
#override
bool operator ==(Object other) =>
(other is UnderTesting) ? (a == other.a && b == other.b) : false;
#override
int get hashCode => hash2(a, b);
}
void main() {
test("compare objects", () {
final obj1 = UnderTesting(a: 1, b: 2);
final obj2 = UnderTesting(a: 1, b: 2);
expect(obj1, equals(obj2)); // All tests passed!
});
}
I can recommend the quiver package for making easy hashCode implementations: https://pub.dev/documentation/quiver/latest/quiver.core/quiver.core-library.html

How to set a default value for an optional positional parameter of type Function?

I am passing a Function as an optional parameter to the constructor but I can't assign a default value.
void main() {
Person p = Person();
print(p.foo('Hello'));
}
class Person {
final String Function(String) foo;
Person({this.foo});
}
now trying to assign a default value: Person({this.foo = (val) {return val;});
produces the error: Error: Not a constant expression. I am aware the parameter must be const but using const or even static infront of (val) {return val;} does not work.
Does anyone have an idea how to solve this problem?
You can try this:
void main() {
Person pLower = Person(foo: (a) => a.toLowerCase());
print(pLower.foo('Hello'));
Person pDefault = Person();
print(pDefault.foo('Hello'));
}
class Person {
static String defaultFoo(String a) => a.toUpperCase();
final String Function(String) foo;
Person({this.foo = defaultFoo});
}
Output
hello
HELLO
You can only use constant values (aka. compile-time constants) as default values.
You cannot create a constant function literal, so there is no way to write the function in-line in the constructor.
However, references to top-level or static functions are constants, so you can declare the default value function as a static function or top-level function.
void main() {
Person p = Person();
print(p.foo('Hello')); // Prints "Hello"
}
class Person {
final String Function(String) foo;
Person({this.foo = _identity});
static String _identity(String value) => value;
}
// or as top-level.
// String _identity(String value) => value;
You can (and should) choose to make the function public if the default value is on an instance method, and you expect anyone to extend or implement your class. In that case, they need to declare the same default value.
Another option, which is often at least as useful, is to not use a default value, but replace a null before using the value:
class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = foo ?? _identity;
static String _identity(String value) => value;
}
or even using a non-constant value:
class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = (foo ?? (String x) => x);
}
For a constructor, it makes very little difference. If it was an instance method instead, using ?? to replace null avoids subclasses having to use the exact same function as default value.
Personally I recommend always using ?? instead of a default value. It's more flexible since it allows non-constant values. For non-function default values, you'll have to document the default behavior instead of just letting the dartDoc show {int x = 42}, but for functions, you'll have to document them anyway.

Test stream with wrapped array

I'm trying to test that the function emitArray emits a Response.Success an its value is ['test'].
If I emit a List<String> everything works as expected, but once I wrap the result list in a Response<List<String>> the test fails.
The result is emitted, but it fails when comparing with the expected result.
I'm wondering if it's related to the implementation of == in Response.Success, I'm using the default implementation that the IDE provides.
This is not the real code I have, it's just a simple example that is easier to understand to try to identify the issue.
This is my class to test:
class ListResponse {
final _array = BehaviorSubject<Response<List<String>>>();
Stream<Response<List<String>>> get array => _array.stream;
Future<void> emitArray() async {
_array.add(Response.success(['test']));
}
void dispose() {
_array.close();
}
}
This is my test:
void main() {
ListResponse underTest;
setUp(() {
underTest = ListResponse();
});
test('It should emit array', () {
final array = Response.success(['test']);
expect(
underTest.array,
emitsInOrder([
array,
emitsDone,
]),
);
underTest.emitArray();
underTest.dispose();
});
}
This is the error it throws:
Expected: should do the following in order:
• emit an event that SuccessResponse<List<String>>:<SuccessResponse{value: [test]}>
• be done
Actual: <Instance of 'BehaviorSubject<Response<List<String>>>'>
Which: emitted • SuccessResponse{value: [test]}
x Stream closed.
which didn't emit an event that SuccessResponse<List<String>>:<SuccessResponse{value: [test]}>
This is the Response class
class Response<T> {
Response._();
factory Response.success(T value) = SuccessResponse<T>;
factory Response.error(Exception error) = ErrorResponse<T>;
}
class ErrorResponse<T> extends Response<T> {
ErrorResponse(this.error): super._();
final Exception error;
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ErrorResponse &&
runtimeType == other.runtimeType &&
error == other.error;
#override
int get hashCode => error.hashCode;
#override
String toString() {
return 'ErrorResponse{error: $error}';
}
}
class SuccessResponse<T> extends Response<T> {
SuccessResponse(this.value): super._();
final T value;
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is SuccessResponse &&
runtimeType == other.runtimeType &&
value == other.value;
#override
int get hashCode => value.hashCode;
#override
String toString() {
return 'SuccessResponse{value: $value}';
}
}
I'm wondering if it's related to the implementation of == in Response.Success
Exactly. This particular test is failing because you can't compare Lists with ==:
abstract class List<E> implements EfficientLengthIterable<E> {
...
/**
* Whether this list is equal to [other].
*
* Lists are, by default, only equal to themselves.
* Even if [other] is also a list, the equality comparison
* does not compare the elements of the two lists.
*/
bool operator ==(Object other);
}
As a workaround you can change the implementation to compare objects' string representations instead:
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is SuccessResponse &&
runtimeType == other.runtimeType &&
value.toString() == other.value.toString();
Interestingly, passing unwrapped List<String>s objects passes test. That happens because StreamMatcher uses equals() from matcher package to match events, and equals() can match lists and maps. It first tries to match objects with ==, then checks whether they are Iterable/Set/Map (and deep matches them recursively), and then reports mismatch error.

Why does Dart sometimes consider Objects as not equal if one is const?

I've got ClassA which holds a a List<ClassB>. ClassB has an string attribute.
If I now have one const Object of ClassA with a list of an object of ClassB completly identical to another non const Object of ClassA with the exact same object of ClassB then these two are not treated as equal.
Why? I could not find any documentation referencing this occurance when looking any documentation regarding equality.
Here's the code:
import 'package:test/test.dart';
void main() {
test('equal', () {
const ClassA a1 = ClassA(list: [ClassB(text: "Mo")]);
ClassA a2 = ClassA(list: [ClassB(text: "Mo"),]);
expect(const [ClassB(text: "Mo")], [ClassB(text: "Mo")]);//true
expect(a1, equals(a2)); //false. Is only true when a2 is const.
});
}
class ClassB {
final String text;
const ClassB({this.text});
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ClassB &&
runtimeType == other.runtimeType &&
text == other.text;
#override
int get hashCode => text.hashCode;
}
class ClassA {
final List<ClassB> list;
const ClassA({this.list});
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ClassA &&
runtimeType == other.runtimeType &&
list == other.list;
#override
int get hashCode => list.hashCode;
}
I expected a1 and a2 as being equal.
The problem is that list and other.list are only equal if they are both const (and with the same, const values, of course), as they are then the same object.
package:collections has some useful comparison tools.
Your equals operator can be rewritten as:
import 'package:collection/collection.dart';
...
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ClassA && ListEquality<ClassB>().equals(list, other.list);
You will also need to change your implementation of hashCode as, with the change above, the classes are now equal but have differing hashCodes. See edit below...
See also.
Edit
class ClassA {
final List<ClassB> list;
final ListEquality<ClassB> equality = const ListEquality<ClassB>();
const ClassA({this.list});
#override
bool operator ==(Object other) {
return identical(this, other) ||
other is ClassA && equality.equals(list, other.list);
}
#override
int get hashCode => equality.hash(list);
}

Resources