diff --git a/lib/bloc/ls_dir.dart b/lib/bloc/ls_dir.dart index aa0a2214..66012907 100644 --- a/lib/bloc/ls_dir.dart +++ b/lib/bloc/ls_dir.dart @@ -1,11 +1,12 @@ import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file/data_source.dart'; import 'package:nc_photos/use_case/ls.dart'; -class LsDirBlocItem { +class LsDirBlocItem with EquatableMixin { LsDirBlocItem(this.file, this.children); @override @@ -32,6 +33,12 @@ class LsDirBlocItem { return product; } + @override + get props => [ + file, + children, + ]; + final File file; /// Child directories under this directory @@ -77,18 +84,25 @@ class LsDirBlocQuery extends LsDirBlocEvent { final int depth; } -abstract class LsDirBlocState { +abstract class LsDirBlocState with EquatableMixin { const LsDirBlocState(this.account, this.root, this.items); @override toString() { return "$runtimeType {" "account: $account, " - "root: ${root.path}" + "root: ${root.path}, " "items: List {length: ${items.length}}, " "}"; } + @override + get props => [ + account, + root, + items, + ]; + final Account? account; final File root; final List items; @@ -121,12 +135,20 @@ class LsDirBlocFailure extends LsDirBlocState { "}"; } + @override + get props => [ + ...super.props, + exception, + ]; + final dynamic exception; } /// A bloc that return all directories under a dir recursively class LsDirBloc extends Bloc { - LsDirBloc() : super(LsDirBlocInit()); + LsDirBloc({ + this.fileRepo = const FileRepo(const FileWebdavDataSource()), + }) : super(LsDirBlocInit()); @override mapEventToState(LsDirBlocEvent event) async* { @@ -150,7 +172,7 @@ class LsDirBloc extends Bloc { final product = []; var files = _cache[ev.root.path]; if (files == null) { - files = (await Ls(FileRepo(FileWebdavDataSource()))(ev.account, ev.root)) + files = (await Ls(fileRepo)(ev.account, ev.root)) .where((f) => f.isCollection ?? false) .toList(); _cache[ev.root.path] = files; @@ -165,6 +187,8 @@ class LsDirBloc extends Bloc { return product; } + final FileRepo fileRepo; + final _cache = >{}; static final _log = Logger("bloc.ls_dir.LsDirBloc"); diff --git a/lib/entity/file.dart b/lib/entity/file.dart index 2b64bf08..df558e56 100644 --- a/lib/entity/file.dart +++ b/lib/entity/file.dart @@ -456,7 +456,7 @@ extension FileExtension on File { } class FileRepo { - FileRepo(this.dataSrc); + const FileRepo(this.dataSrc); /// See [FileDataSource.list] Future> list(Account account, File root) => diff --git a/lib/entity/file/data_source.dart b/lib/entity/file/data_source.dart index bc65797c..f7695b94 100644 --- a/lib/entity/file/data_source.dart +++ b/lib/entity/file/data_source.dart @@ -22,6 +22,8 @@ import 'package:uuid/uuid.dart'; import 'package:xml/xml.dart'; class FileWebdavDataSource implements FileDataSource { + const FileWebdavDataSource(); + @override list( Account account, diff --git a/pubspec.lock b/pubspec.lock index f4441048..6ee8e29a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -50,6 +50,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.0.0" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.0" boolean_selector: dependency: transitive description: @@ -412,6 +419,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + mocktail: + dependency: transitive + description: + name: mocktail + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" nested: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d47b8633..1315afc1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,6 +79,7 @@ dependencies: dev_dependencies: test: any + bloc_test: any # flutter_test: # sdk: flutter # integration_test: diff --git a/test/bloc/ls_dir_test.dart b/test/bloc/ls_dir_test.dart new file mode 100644 index 00000000..9fc4458c --- /dev/null +++ b/test/bloc/ls_dir_test.dart @@ -0,0 +1,205 @@ +import 'dart:typed_data'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:nc_photos/account.dart'; +import 'package:nc_photos/bloc/ls_dir.dart'; +import 'package:nc_photos/entity/file.dart'; +import 'package:nc_photos/or_null.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +void main() { + final _buildBloc = () => LsDirBloc(fileRepo: _MockFileRepo()); + final _buildAccount = + () => Account("http", "example.com", "admin", "pass", [""]); + + group("ListDir", () { + group("LsDirBlocQuery", () { + test("initial state", () { + final bloc = _buildBloc(); + expect(bloc.state.account, null); + expect(bloc.state.root, File(path: "")); + expect(bloc.state.items, []); + }); + + blocTest( + "inital", + build: _buildBloc, + expect: () => [], + ); + + blocTest( + "query 1 subdir", + build: _buildBloc, + act: (bloc) => bloc.add(LsDirBlocQuery( + _buildAccount(), File(path: "remote.php/dav/files/admin"))), + expect: () => [ + LsDirBlocLoading( + _buildAccount(), File(path: "remote.php/dav/files/admin"), []), + LsDirBlocSuccess( + _buildAccount(), File(path: "remote.php/dav/files/admin"), [ + LsDirBlocItem( + File( + path: "remote.php/dav/files/admin/d1", + isCollection: true, + ), + null, + ), + ]), + ], + ); + + blocTest( + "query n subdir", + build: _buildBloc, + act: (bloc) => bloc.add(LsDirBlocQuery( + _buildAccount(), File(path: "remote.php/dav/files/admin/d1"))), + expect: () => [ + LsDirBlocLoading( + _buildAccount(), File(path: "remote.php/dav/files/admin/d1"), []), + LsDirBlocSuccess( + _buildAccount(), File(path: "remote.php/dav/files/admin/d1"), [ + LsDirBlocItem( + File( + path: "remote.php/dav/files/admin/d1/d2-1", + isCollection: true, + ), + null, + ), + LsDirBlocItem( + File( + path: "remote.php/dav/files/admin/d1/d2-2", + isCollection: true, + ), + null, + ), + ]), + ], + ); + + blocTest( + "query 0 subdir", + build: _buildBloc, + act: (bloc) => bloc.add(LsDirBlocQuery( + _buildAccount(), File(path: "remote.php/dav/files/admin/d1/d2-2"))), + expect: () => [ + LsDirBlocLoading(_buildAccount(), + File(path: "remote.php/dav/files/admin/d1/d2-2"), []), + LsDirBlocSuccess(_buildAccount(), + File(path: "remote.php/dav/files/admin/d1/d2-2"), []), + ], + ); + + blocTest( + "query depth 2", + build: _buildBloc, + act: (bloc) => bloc.add(LsDirBlocQuery( + _buildAccount(), File(path: "remote.php/dav/files/admin"), + depth: 2)), + expect: () => [ + LsDirBlocLoading( + _buildAccount(), File(path: "remote.php/dav/files/admin"), []), + LsDirBlocSuccess( + _buildAccount(), File(path: "remote.php/dav/files/admin"), [ + LsDirBlocItem( + File( + path: "remote.php/dav/files/admin/d1", + isCollection: true, + ), + [ + LsDirBlocItem( + File( + path: "remote.php/dav/files/admin/d1/d2-1", + isCollection: true, + ), + null, + ), + LsDirBlocItem( + File( + path: "remote.php/dav/files/admin/d1/d2-2", + isCollection: true, + ), + null, + ), + ], + ), + ]), + ], + ); + }); + }); +} + +class _MockFileRepo implements FileRepo { + @override + Future copy(Object account, File f, String destination, + {bool? shouldOverwrite}) { + throw UnimplementedError(); + } + + @override + Future createDir(Account account, String path) { + throw UnimplementedError(); + } + + @override + FileDataSource get dataSrc => throw UnimplementedError(); + + @override + Future getBinary(Account account, File file) { + throw UnimplementedError(); + } + + @override + Future> list(Account account, File root) async { + await Future.delayed(const Duration(seconds: 1)); + return [ + File( + path: "remote.php/dav/files/admin/test1.jpg", + ), + File( + path: "remote.php/dav/files/admin/d1", + isCollection: true, + ), + File( + path: "remote.php/dav/files/admin/d1/test2.jpg", + ), + File( + path: "remote.php/dav/files/admin/d1/d2-1", + isCollection: true, + ), + File( + path: "remote.php/dav/files/admin/d1/d2-2", + isCollection: true, + ), + File( + path: "remote.php/dav/files/admin/d1/d2-1/d3", + isCollection: true, + ), + ].where((element) => path.dirname(element.path) == root.path).toList(); + } + + @override + Future move(Account account, File f, String destination, + {bool? shouldOverwrite}) { + throw UnimplementedError(); + } + + @override + Future putBinary(Account account, String path, Uint8List content) { + throw UnimplementedError(); + } + + @override + Future remove(Account account, File file) { + throw UnimplementedError(); + } + + @override + Future updateProperty(Account account, File file, + {OrNull? metadata, + OrNull? isArchived, + OrNull? overrideDateTime}) { + throw UnimplementedError(); + } +}