import 'dart:async'; import 'dart:convert'; import 'package:background_downloader/background_downloader.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; class _Storage implements PersistentStorage { @override Future initialize() async {} @override Future> retrieveAllTaskRecords() async => []; @override Future removeResumeData(String? id) async {} @override Future removePausedTask(String? id) async {} @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; const native = MethodChannel('com.bbflight.background_downloader'); const connectivity = MethodChannel('dev.fluttercommunity.plus/connectivity'); const events = MethodChannel('dev.fluttercommunity.plus/connectivity_status'); const codec = StandardMethodCodec(); late FileDownloader downloader; final oldPlatform = debugDefaultTargetPlatformOverride; setUpAll(() async { debugDefaultTargetPlatformOverride = TargetPlatform.android; messenger.setMockMethodCallHandler(connectivity, (_) async => ['wifi']); messenger.setMockMethodCallHandler(events, (_) async => null); downloader = FileDownloader(persistentStorage: _Storage()); await downloader.ready; }); tearDownAll(() { downloader.destroy(); debugDefaultTargetPlatformOverride = oldPlatform; messenger.setMockMethodCallHandler(native, null); messenger.setMockMethodCallHandler(connectivity, null); messenger.setMockMethodCallHandler(events, null); }); Future enqueueChild(String id) async { final reply = Completer(); final parent = ParallelDownloadTask( taskId: 'parent-$id', url: 'https://example.test/video.mp4', ); final child = DownloadTask( taskId: id, url: parent.url, group: FileDownloader.chunkGroup, ); // Exercise the real plugin background handler and its JSON worker. // ignore: deprecated_member_use messenger.handlePlatformMessage( 'com.bbflight.background_downloader.background', codec.encodeMethodCall( MethodCall('enqueueChild', [ jsonEncode(parent.toJson()), jsonEncode(child.toJson()), ]), ), reply.complete, ); return codec.decodeEnvelope( (await reply.future.timeout(const Duration(seconds: 3)))!, ); } test('child acknowledgement waits for native scheduling', () async { final dispatched = Completer(); final scheduled = Completer(); messenger.setMockMethodCallHandler(native, (call) async { if (call.method == 'enqueue') { dispatched.complete(); return scheduled.future; } return true; }); var replied = false; final reply = enqueueChild('pending-child').then((value) { replied = true; return value; }); await dispatched.future.timeout(const Duration(seconds: 3)); await Future.delayed(Duration.zero); expect(replied, isFalse); scheduled.complete(true); expect(await reply, isTrue); }); test( 'native child dispatch error returns failure without a detached error', () async { messenger.setMockMethodCallHandler(native, (call) async { throw PlatformException( code: 'scheduler_rejected', message: 'Fixture rejection', ); }); expect(await enqueueChild('throwing-child'), isFalse); await Future.delayed(const Duration(milliseconds: 150)); }, ); test('child enqueue rejection is not acknowledged as success', () async { var attempts = 0; messenger.setMockMethodCallHandler(native, (call) async { if (call.method == 'enqueue') attempts++; return false; }); final accepted = await enqueueChild('rejected-child'); // Drain the old detached 100ms enqueue before asserting/teardown too. await Future.delayed(const Duration(milliseconds: 150)); expect(attempts, 1); expect(accepted, isFalse); }); }