Skip to content

Commit cfe97c0

Browse files
committed
feature: serializable actions
1 parent 112e013 commit cfe97c0

File tree

7 files changed

+142
-5
lines changed

7 files changed

+142
-5
lines changed

lib/src/action.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
import 'dart:convert';
2+
13
import 'package:meta/meta.dart';
24

35
/// Base abstract class to make your custom actions more uniform
4-
///
6+
///
57
/// ### Examples
6-
///
8+
///
79
/// ```dart
810
/// @immutable
911
/// class CompleteTodo extends PayloadAction<Todo, void, void> {
1012
/// const CompleteTodo(Todo todo) : super(payload: todo);
1113
/// }
1214
/// ```
13-
///
15+
///
1416
/// ```dart
1517
/// @immutable
1618
/// class Fulfilled<T, P, M> extends PayloadAction<P, Meta<M>, dynamic> {
@@ -29,4 +31,10 @@ abstract class PayloadAction<Payload, Meta, Error> {
2931
this.meta,
3032
this.error,
3133
});
34+
35+
Map<String, dynamic> toJson() => {
36+
'payload': jsonDecode(jsonEncode(payload)),
37+
'meta': jsonDecode(jsonEncode(meta)),
38+
'error': jsonDecode(jsonEncode(error)),
39+
};
3240
}

lib/src/async_thunk.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
import 'dart:convert';
23
import 'package:meta/meta.dart';
34
import 'package:redux/redux.dart';
45
import 'package:redux_thunk/redux_thunk.dart';
@@ -13,6 +14,9 @@ class Meta<T> {
1314
final String requestId;
1415

1516
const Meta(this.arg, this.requestId);
17+
18+
Map<String, dynamic> toJson() =>
19+
{'requestId': requestId, 'arg': jsonDecode(jsonEncode(arg))};
1620
}
1721

1822
/// Action that gets dispatched when an `AsyncThunk`

test/fakes/test_actions.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:meta/meta.dart';
2+
import 'package:redux_toolkit/redux_toolkit.dart';
23

34
import '../fakes/test_state.dart';
45

@@ -21,11 +22,17 @@ abstract class ErrorAction<T extends Exception> {
2122
class RequestItemsAction {}
2223

2324
class FetchItemsSuccessfulAction extends Action<List<Item>> {
24-
const FetchItemsSuccessfulAction({ List<Item> payload }) : super(payload: payload);
25+
const FetchItemsSuccessfulAction({List<Item> payload})
26+
: super(payload: payload);
2527
}
2628

2729
class FetchItemsErrorAction extends ErrorAction<Exception> {
28-
const FetchItemsErrorAction({ error }) : super(error: error);
30+
const FetchItemsErrorAction({error}) : super(error: error);
2931
}
3032

3133
class SomeUnhandledAction {}
34+
35+
class TestPayloadAction extends PayloadAction {
36+
const TestPayloadAction({dynamic payload, dynamic meta, dynamic error})
37+
: super(payload: payload, meta: meta, error: error);
38+
}

test/fakes/test_model.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class TestModel {
2+
final String id;
3+
final String title;
4+
final bool completed;
5+
6+
const TestModel({this.id, this.title, this.completed});
7+
8+
Map<String, dynamic> toJson() =>
9+
{'id': id, 'title': title, 'completed': completed};
10+
}

test/redux_toolkit_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import 'tests/action_test.dart' as action_tests;
12
import 'tests/async_thunk_test.dart' as async_thunk_tests;
23
import 'tests/create_reducer_test.dart' as create_reducer_test;
34

45
void main() {
6+
action_tests.main();
57
async_thunk_tests.main();
68
create_reducer_test.main();
79
}

test/tests/action_test.dart

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import 'dart:convert';
2+
3+
import 'package:test/test.dart';
4+
5+
import '../fakes/test_actions.dart';
6+
import '../fakes/test_model.dart';
7+
8+
void main() {
9+
group('payload action', () {
10+
test('should be json serializable', () {
11+
final action = TestPayloadAction(
12+
payload: 'Serializable payload',
13+
meta: 'Serializable meta',
14+
error: 'Serializable error');
15+
final actual = jsonDecode(jsonEncode(action));
16+
final expected = {
17+
'payload': 'Serializable payload',
18+
'meta': 'Serializable meta',
19+
'error': 'Serializable error',
20+
};
21+
expect(actual, equals(expected));
22+
});
23+
24+
test('should serialize nested objects within payload', () {
25+
final action = TestPayloadAction(
26+
payload: TestModel(
27+
id: '9f4772ab-8756-47e1-89a0-69d3e7a056d5',
28+
title: 'Lorem ipsum dolor sit amet',
29+
completed: false),
30+
meta: 'Serializable meta',
31+
error: 'Serializable error');
32+
final actual = jsonDecode(jsonEncode(action));
33+
final expected = {
34+
'payload': {
35+
'id': '9f4772ab-8756-47e1-89a0-69d3e7a056d5',
36+
'title': 'Lorem ipsum dolor sit amet',
37+
'completed': false
38+
},
39+
'meta': 'Serializable meta',
40+
'error': 'Serializable error',
41+
};
42+
expect(actual, equals(expected));
43+
});
44+
45+
test('should serialize nested objects within meta', () {
46+
final action = TestPayloadAction(
47+
payload: 'Serializable payload',
48+
meta: TestModel(
49+
id: '9f4772ab-8756-47e1-89a0-69d3e7a056d5',
50+
title: 'Lorem ipsum dolor sit amet',
51+
completed: false),
52+
error: 'Serializable error');
53+
final actual = jsonDecode(jsonEncode(action));
54+
final expected = {
55+
'payload': 'Serializable payload',
56+
'meta': {
57+
'id': '9f4772ab-8756-47e1-89a0-69d3e7a056d5',
58+
'title': 'Lorem ipsum dolor sit amet',
59+
'completed': false
60+
},
61+
'error': 'Serializable error',
62+
};
63+
expect(actual, equals(expected));
64+
});
65+
66+
test('should serialize nested objects within errors', () {
67+
final action = TestPayloadAction(
68+
payload: 'Serializable payload',
69+
meta: 'Serializable meta',
70+
error: TestModel(
71+
id: '9f4772ab-8756-47e1-89a0-69d3e7a056d5',
72+
title: 'Lorem ipsum dolor sit amet',
73+
completed: false));
74+
final actual = jsonDecode(jsonEncode(action));
75+
final expected = {
76+
'payload': 'Serializable payload',
77+
'meta': 'Serializable meta',
78+
'error': {
79+
'id': '9f4772ab-8756-47e1-89a0-69d3e7a056d5',
80+
'title': 'Lorem ipsum dolor sit amet',
81+
'completed': false
82+
},
83+
};
84+
expect(actual, equals(expected));
85+
});
86+
});
87+
}

test/tests/async_thunk_test.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:convert';
2+
13
import 'package:test/test.dart';
24
import 'package:mockito/mockito.dart';
35
import 'package:redux/redux.dart';
@@ -52,5 +54,22 @@ void main() {
5254
.every((element) => element == requestId),
5355
isTrue);
5456
});
57+
58+
test('should dispatch serializable actions', () async {
59+
final thunk = StringLength('hello');
60+
await thunk.call(store);
61+
verify(store.dispatch(argThat(predicate(
62+
(arg) => jsonDecode(jsonEncode(arg)) is Map<String, dynamic>))));
63+
});
64+
65+
test('should have serializable metadata', () async {
66+
final meta = Meta(null, '3c26a440-d9a7-4fc2-ac2a-8524de6f1f19');
67+
final actual = meta.toJson();
68+
final expected = {
69+
'arg': null,
70+
'requestId': '3c26a440-d9a7-4fc2-ac2a-8524de6f1f19',
71+
};
72+
expect(actual, equals(expected));
73+
});
5574
});
5675
}

0 commit comments

Comments
 (0)