Skip to content

Commit

Permalink
1.0.0 (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
moseskarunia authored Feb 2, 2021
1 parent b0625da commit bc0899e
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 98 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [1.0.0] - 2 February 2020
- **Breaking:** `CornerstonePersistentRepositoryMixin`'s `loadData()` is now removed. You just need to add dependency of `convertToSnapshot` to persistent repository with `CornerstonePersistentRepositoryMixin`. The mixin now handles loading data, and assigning it to `repo.snapshot` (Means less code to write!). The snapshot is now named `snapshot`, and declared in the mixin (you can override it). If you want to use other names for snapshot, you need to override `load`. You can override `snapshot` if you want to assign default value to it (See `auto_persistent_people_repository.dart` in example).
- First stable release of cornerstone! From now on, breaking changes will only introduced with new major version.

## [0.1.2] - 20 January 2020
- Revert: `LocallyPersistentRepository` is now an abstract class again. In dart, you can use an abstract class as a mixin, but you can't have your classes extends from a mixin. You can always implements a mixin, but it will lose some built-in implementation code (In this case, e.g. `storageName`). So by making it an abstract class again, it will give you more flexibility with nothing to give up. Therefore this revert will not breaking anything. _(In case you are wondering, This is not the case with `CornerstonePersistentRepositoryMixin`. If I make it an abstract and extends from `LocallyPersistentRepository`, then it can no longer be used as a mixin.)_
- Documentation: `MultipleGetterPersisitentRepository` should be written as `CornerstonePersistentRepositoryMixin` on `ConvertToSnapshot`'s dartdoc.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Most modern apps revolves around CRUD operations through an API & Data Persisten

### CornerstonePersistentRepositoryMixin

A mixin to make your repository persistable easily. I use [Hive](https://pub.dev/packages/hive) in order to achieve this. See the dartdoc in the file & implementation example in the example folder for more info.
A mixin to make your repository persistable locally easily. I use [Hive](https://pub.dev/packages/hive) in order to achieve this. See the dartdoc in the file & implementation example in the example folder for more info and usage example.

# The Architecture

Expand Down
27 changes: 7 additions & 20 deletions example/lib/repositories/auto_persistent_people_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,34 +38,21 @@ class AutoPersistentPeopleRepositoryImpl
extends AutoPersistentPeopleRepository {
final Clock clock;
final HiveInterface hive;
@override
final ConvertToFailure convertToFailure;
final ConvertToSnapshot<Map<String, dynamic>, NewPeopleSnapshot>
convertToSnapshot;

NewPeopleSnapshot data = NewPeopleSnapshot(timestamp: (const Clock()).now());
@override
NewPeopleSnapshot snapshot;

Map<String, dynamic> get asJson => data.toJson();
Map<String, dynamic> get asJson => snapshot.toJson();

AutoPersistentPeopleRepositoryImpl({
@required this.hive,
@required this.convertToFailure,
@required this.convertToSnapshot,
this.clock = const Clock(),
});

@override
Future<Either<Failure, NewPeopleSnapshot>> load() async {
/// Doesn't need to wrap in try catch since basically this function just
/// calls [loadData], which already wrapped in try catch.
final result = await loadData();

if (result.isLeft()) {
return Left((result as Left).value);
}

data = NewPeopleSnapshot.fromJson((result as Right).value);

return Right(data);
}
}) : snapshot = NewPeopleSnapshot(timestamp: (const Clock()).now());

@override
Future<Either<Failure, NewPeopleSnapshot>> getPeople() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import 'package:clock/clock.dart';
import 'package:cornerstone/cornerstone.dart';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:example/data_sources/people_data_source.dart';
import 'package:example/entities/person.dart';
import 'package:example/repositories/auto_persistent_people_repository.dart';
Expand All @@ -17,6 +15,9 @@ class MockBox extends Mock implements Box {}

class MockConvert extends Mock implements ConvertToFailure {}

class MockConvertToSnapshot extends Mock
implements ConvertToSnapshot<Map<String, dynamic>, NewPeopleSnapshot> {}

void main() {
group('AutoPersistentPeopleRepositoryImpl', () {
final jsonListFixture = [
Expand Down Expand Up @@ -50,16 +51,19 @@ void main() {
MockBox box;
MockHive hive;
MockConvert convert;
MockConvertToSnapshot convertToSnapshot;
AutoPersistentPeopleRepositoryImpl repo;

setUp(() {
box = MockBox();
hive = MockHive();
convert = MockConvert();
convertToSnapshot = MockConvertToSnapshot();

repo = AutoPersistentPeopleRepositoryImpl(
hive: hive,
convertToFailure: convert,
convertToSnapshot: convertToSnapshot,
clock: Clock.fixed(DateTime(2020, 10, 10)),
);
when(hive.openBox(any)).thenAnswer((_) async => box);
Expand All @@ -77,53 +81,16 @@ void main() {
});
});

test('storageName should be PeopleRepoWithBuiltInHiveImpl', () {
expect(repo.storageName, 'PeopleRepoWithBuiltInHiveImpl');
test('storageName should be AutoPersistentPeopleRepositoryImpl', () {
expect(repo.storageName, 'AutoPersistentPeopleRepositoryImpl');
});
test('asJson', () {
repo.data = snapFixture;
repo.snapshot = snapFixture;
expect(repo.asJson, snapJsonFixture);
});

test('getPeople should throw UnimplementedError', () {
expectLater(() => repo.getPeople(), throwsA(isA<UnimplementedError>()));
});

group('load', () {
test(
'should catch exception and return result of convertToFailure in Left',
() async {
final e = HiveError('TEST_ERROR');
when(hive.openBox(any)).thenThrow(e);
when(convert(any)).thenReturn(Failure<dynamic>(name: 'TEST_ERROR'));

final result = await repo.load();

expect(
(result as Left).value,
Failure<dynamic>(name: 'TEST_ERROR'),
);
verifyInOrder([hive.openBox(repo.storageName), convert(e)]);
},
);

test('should call loadData in mixin and return Right', () async {
EquatableConfig.stringify = true;
when(box.toMap()).thenReturn(snapJsonFixture);
final result = await repo.load();
expect(
repo.data,
NewPeopleSnapshot(data: peopleListFixture, timestamp: dateFixture),
);
expect(
(result as Right).value,
NewPeopleSnapshot(data: peopleListFixture, timestamp: dateFixture),
);
verifyInOrder([
hive.openBox(repo.storageName),
box.toMap(),
]);
});
});
});
}
20 changes: 12 additions & 8 deletions lib/src/utilities/cornerstone_persistent_repository_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ mixin CornerstonePersistentRepositoryMixin<Snap>
on LocallyPersistentRepository<Snap> {
HiveInterface get hive;
ConvertToFailure get convertToFailure;
ConvertToSnapshot<Map<String, dynamic>, Snap> get convertToSnapshot;

Snap snapshot;

@override
@visibleForOverriding
Expand All @@ -42,23 +45,24 @@ mixin CornerstonePersistentRepositoryMixin<Snap>
}
}

/// Loads data from hive and returns the `Map<String,dynamic>` data of
/// your snapshot. Call it from your load() which overrided from
/// [LocallyPersistentRepository].
@protected
@visibleForTesting
Future<Either<Failure, Map<String, dynamic>>> loadData() async {
@override
Future<Either<Failure, Snap>> load() async {
try {
final box = await hive.openBox(storageName);

/// For some reason, this is the only safe way I can get box data as
/// `Map<String, dynamic>` consistently. Using cast throws an error.
/// `Map<String, dynamic>` (Using cast throws an error).
///
/// Don't forget to add `anyMap: true` to your snapshot's
/// `@JsonSerializable` annotation.
///
/// See this [issue](https://github.com/hivedb/hive/issues/522) if you
/// want to get updated on this.
final result = box.toMap().map((k, e) => MapEntry(k.toString(), e));

return Right(result);
snapshot = convertToSnapshot(result);

return Right(snapshot);
} catch (e) {
return Left(convertToFailure(e));
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: cornerstone
description: A library written in pure dart to help devs set up dart projects with a proper architecture without having to write a lot of boilerplate code. Inspired by clean architecture.
version: 0.1.2
version: 1.0.0
repository: https://www.github.com/moseskarunia/cornerstone

environment:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class MockBox extends Mock implements Box {}

class MockConvertToFailure extends Mock implements ConvertToFailure {}

class MockConvertToSnapshot extends Mock
implements ConvertToSnapshot<Map<String, dynamic>, FruitSnapshot> {}

class FruitSnapshot extends CornerstoneSnapshot {
final List<String> data;

Expand Down Expand Up @@ -41,39 +44,47 @@ abstract class FruitRepository
class FruitRepositoryImpl extends FruitRepository {
@override
final ConvertToFailure convertToFailure;
final ConvertToSnapshot<Map<String, dynamic>, FruitSnapshot>
convertToSnapshot;
final HiveInterface hive;

FruitSnapshot data = FruitSnapshot(
data: [],
timestamp: DateTime(2020, 10, 10),
);
@override
FruitSnapshot snapshot;

FruitRepositoryImpl({
@required this.hive,
@required this.convertToFailure,
});
@required this.convertToSnapshot,
}) : snapshot = FruitSnapshot(
data: [],
timestamp: DateTime.parse('2021-01-10T10:00:00Z').toLocal(),
);

@override
Map<String, dynamic> get asJson => <String, dynamic>{
'data': data.data,
'timestamp': data.timestamp.toUtc().toIso8601String()
'data': snapshot?.data,
'timestamp': snapshot?.timestamp?.toUtc()?.toIso8601String()
};

@override
Future<Either<Failure, FruitSnapshot>> load() => throw UnimplementedError();
}

void main() {
final dateFixture = DateTime.parse('2021-01-10T10:00:00Z').toLocal();
MockBox box;
MockConvertToFailure convertToFailure;
MockConvertToSnapshot convertToSnapshot;
FruitRepositoryImpl repo;
MockHive hive;

setUp(() {
convertToSnapshot = MockConvertToSnapshot();
convertToFailure = MockConvertToFailure();
box = MockBox();
hive = MockHive();
repo = FruitRepositoryImpl(hive: hive, convertToFailure: convertToFailure);
repo = FruitRepositoryImpl(
hive: hive,
convertToFailure: convertToFailure,
convertToSnapshot: convertToSnapshot,
);

when(hive.openBox(any)).thenAnswer((_) async => box);
});
Expand Down Expand Up @@ -144,30 +155,57 @@ void main() {
});
});

group('loadData', () {
group('load', () {
test(
'should catch Exception '
'and return the result of convertToFailure', () async {
final e = HiveError('TEST_ERROR');
when(box.toMap()).thenThrow(e);
when(convertToFailure(any)).thenReturn(Failure(name: 'TEST_ERROR'));
'should catch Exception '
'and return the result of convertToFailure',
() async {
final e = HiveError('TEST_ERROR');
when(box.toMap()).thenThrow(e);
when(convertToFailure(any)).thenReturn(Failure(name: 'TEST_ERROR'));

final result = await repo.load();

final result = await repo.loadData();
expect((result as Left).value, Failure(name: 'TEST_ERROR'));

expect((result as Left).value, Failure(name: 'TEST_ERROR'));
verifyInOrder([
hive.openBox(repo.storageName),
box.toMap(),
convertToFailure(e),
]);
},
);

test('should assign snapshot', () async {
when(box.toMap()).thenReturn(<String, dynamic>{
'data': ['Apple'],
'timestamp': '2021-01-10T10:00:00Z'
});
when(convertToSnapshot(any)).thenReturn(FruitSnapshot(
data: ['Apple'],
timestamp: dateFixture,
));
final result = await repo.load();
expect(
(result as Right).value,
FruitSnapshot(data: ['Apple'], timestamp: dateFixture),
);
expect(
repo.snapshot,
FruitSnapshot(
data: ['Apple'],
timestamp: dateFixture,
),
);
verifyInOrder([
hive.openBox(repo.storageName),
box.toMap(),
convertToFailure(e),
convertToSnapshot(<String, dynamic>{
'data': ['Apple'],
'timestamp': '2021-01-10T10:00:00Z'
}),
]);
});

test('should return the loaded json', () async {
when(box.toMap()).thenReturn(<String, dynamic>{'name': 'John Doe'});
final result = await repo.loadData();
expect((result as Right).value, <String, dynamic>{'name': 'John Doe'});
});
});
});
}

0 comments on commit bc0899e

Please sign in to comment.