diff --git a/.gitignore b/.gitignore index 05210a7..5ebb05c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ build/ doc/api/ .flutter-plugins -.flutter-plugins-dependencies \ No newline at end of file +.flutter-plugins-dependencies + +coverage/ \ No newline at end of file diff --git a/lib/src/mock_list_result.dart b/lib/src/mock_list_result.dart new file mode 100644 index 0000000..cef6bf9 --- /dev/null +++ b/lib/src/mock_list_result.dart @@ -0,0 +1,24 @@ +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:firebase_storage_mocks/firebase_storage_mocks.dart'; +import 'package:firebase_storage_mocks/src/mock_storage_reference.dart'; + +class MockListResult implements ListResult { + @override + MockFirebaseStorage storage; + + @override + List items; + + @override + List prefixes; + + @override + String? nextPageToken; + + MockListResult({ + required this.storage, + required this.items, + required this.prefixes, + this.nextPageToken, + }); +} diff --git a/lib/src/mock_storage_reference.dart b/lib/src/mock_storage_reference.dart index b80ec67..4df2712 100644 --- a/lib/src/mock_storage_reference.dart +++ b/lib/src/mock_storage_reference.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:firebase_storage_mocks/firebase_storage_mocks.dart'; +import 'package:firebase_storage_mocks/src/mock_list_result.dart'; class MockReference implements Reference { final MockFirebaseStorage _storage; @@ -100,6 +101,34 @@ class MockReference implements Reference { return Future.value(_storage.storedDataMap[_path]); } + @override + Future listAll() { + final normalizedPath = _path.endsWith('/') ? _path : _path + '/'; + final prefixes = [], items = []; + final allPaths = [ + ..._storage.storedDataMap.keys, + ..._storage.storedFilesMap.keys, + ..._storage.storedStringMap.keys + ]; + for (var child in allPaths) { + if (!child.startsWith(normalizedPath)) continue; + final relativeChild = child.substring(normalizedPath.length); + if (relativeChild.contains('/')) { + final prefix = normalizedPath + relativeChild.split('/')[0]; + if (!prefixes.contains(prefix)) prefixes.add(prefix); + } else { + items.add(child); + } + } + prefixes.sort(); + items.sort(); + return Future.value(MockListResult( + items: items.map((item) => MockReference(_storage, item)).toList(), + prefixes: + prefixes.map((prefix) => MockReference(_storage, prefix)).toList(), + storage: _storage)); + } + @override Future updateMetadata(SettableMetadata metadata) { final nonNullMetadata = metadata.asMap() diff --git a/test/firebase_storage_mocks_test.dart b/test/firebase_storage_mocks_test.dart index eca35a5..5f771b3 100644 --- a/test/firebase_storage_mocks_test.dart +++ b/test/firebase_storage_mocks_test.dart @@ -1,12 +1,15 @@ import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; import 'package:file/memory.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:firebase_storage_mocks/firebase_storage_mocks.dart'; +import 'package:firebase_storage_mocks/src/mock_storage_reference.dart'; import 'package:test/test.dart'; final filename = 'someimage.png'; +final random = Random.secure(); void main() { group('MockFirebaseStorage Tests', () { @@ -24,7 +27,7 @@ void main() { test('Puts Data', () async { final storage = MockFirebaseStorage(); final storageRef = storage.ref().child(filename); - final imageData = Uint8List(256); + final imageData = randomData(256); final task = storageRef.putData(imageData); await task; @@ -47,7 +50,7 @@ void main() { group('Gets Data', () { late MockFirebaseStorage storage; late Reference reference; - final imageData = Uint8List(256); + final imageData = randomData(256); setUp(() async { storage = MockFirebaseStorage(); reference = storage.ref().child(filename); @@ -126,9 +129,58 @@ void main() { expect(downloadUrl.contains('some-bucket/o/someimage.png'), isTrue); }); }); + + test('Ref listAll', () async { + final basePath = 'this/is/basic'; + final otherPath = 'another/path'; + final storage = MockFirebaseStorage(); + await storage.ref(basePath + '/listed_data').putData(randomData(255)); + await storage + .ref(basePath + '/subdir2/too_deep_too') + .putFile(getFakeImageFile()); + await storage + .ref(basePath + '/subdir1/too_deep') + .putData(randomData(255)); + await storage + .ref(basePath + '/another_listed_data') + .putData(randomData(255)); + await storage + .ref(otherPath + '/i_will_not_be_listed!') + .putData(randomData(255)); + await storage.ref(basePath + '/file_listed').putFile(getFakeImageFile()); + await storage + .ref(basePath + '/yet_another_listed') + .putData(randomData(255)); + await storage + .ref(basePath + '/subdir1/deep_in_same_subdir') + .putFile(getFakeImageFile()); + await storage.ref(basePath + '/string_listed').putString('dummy string!'); + + final listResult = await storage.ref(basePath).listAll(); + expect(listResult.prefixes.length, 2); + // Results are ordered alphabetically + expectRef(listResult.prefixes[0], name: 'subdir1'); + expectRef(listResult.prefixes[1], name: 'subdir2'); + expect(listResult.items.length, 5); + expectRef(listResult.items[0], name: 'another_listed_data'); + expectRef(listResult.items[1], name: 'file_listed'); + expectRef(listResult.items[2], name: 'listed_data'); + expectRef(listResult.items[3], name: 'string_listed'); + expectRef(listResult.items[4], name: 'yet_another_listed'); + }); }); } +void expectRef(Reference actualReference, {required String name}) { + expect(actualReference, + isA().having((ref) => ref.name, 'Right name', name)); +} + +Uint8List randomData(int n) { + final elements = List.generate(n, (index) => random.nextInt(255)); + return Uint8List.fromList(elements); +} + File getFakeImageFile() { var fs = MemoryFileSystem(); final image = fs.file(filename);