Files
FlClash/test/providers/geo_resource_action_test.dart
T
chen08209 8378c7081c feat(views): rework the proxies and scan pages, lay out records by their parts and unify search
A queued delay test shows a static clock until the task pool runs it, so
only the running tests animate while the list scrolls.

Changelog: Hide timed-out nodes and search every list
2026-10-02 20:32:42 +08:00

244 lines
8.6 KiB
Dart

import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/theme.dart';
import 'package:fl_clash/core/controller.dart';
import 'package:fl_clash/core/interface.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/icons/icons.dart';
import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/manager/status_manager.dart';
import 'package:fl_clash/providers/action.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/providers/core.dart';
import 'package:fl_clash/state.dart';
import 'package:material_ui/material_ui.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import '../helpers/glyph_finders.dart';
class _MockCoreHandlerInterface extends Mock implements CoreHandlerInterface {}
Future<ProviderContainer> _pumpGeoResourceAction(
WidgetTester tester,
CoreHandlerInterface coreInterface,
) async {
final container = ProviderContainer(
overrides: [
coreHandlerProvider.overrideWithValue(
CoreController.scoped(coreInterface),
),
],
);
addTearDown(container.dispose);
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: MaterialApp(
navigatorKey: globalState.navigatorKey,
localizationsDelegates: const [AppLocalizations.delegate],
supportedLocales: AppLocalizations.delegate.supportedLocales,
builder: (context, child) {
globalState.measure = Measure.of(context, 1);
globalState.theme = CommonTheme.of(context, 1);
return StatusManager(child: child!);
},
home: const SizedBox(),
),
),
);
return container;
}
void main() {
testWidgets('passive updates change progress without surfacing messages', (
tester,
) async {
final coreInterface = _MockCoreHandlerInterface();
final container = await _pumpGeoResourceAction(tester, coreInterface);
final action = container.read(geoResourceActionProvider.notifier);
final key = GeoResource.MMDB.updatingKey;
final subscription = container.listen<bool>(
isUpdatingProvider(key),
(_, _) {},
);
addTearDown(subscription.close);
action.handleCoreUpdate('MMDB', true, false, null);
expect(container.read(isUpdatingProvider(key)), isTrue);
action.handleCoreUpdate('MMDB', false, false, 'background failure');
await tester.pump();
expect(container.read(isUpdatingProvider(key)), isFalse);
expect(find.text('background failure'), findsNothing);
await tester.pumpWidget(const SizedBox.shrink());
});
testWidgets('a manually skipped update is shown as info', (tester) async {
final coreInterface = _MockCoreHandlerInterface();
when(() => coreInterface.updateGeoData('MMDB')).thenAnswer((_) async => '');
final container = await _pumpGeoResourceAction(tester, coreInterface);
final action = container.read(geoResourceActionProvider.notifier);
final key = GeoResource.MMDB.updatingKey;
await action.updateGeoResource(GeoResource.MMDB);
expect(container.read(isUpdatingProvider(key)), isTrue);
action.handleCoreUpdate('MMDB', false, true, null);
await tester.pump();
expect(container.read(isUpdatingProvider(key)), isFalse);
expect(
find.text(currentAppLocalizations.geoSkipped(GeoResource.MMDB.name)),
findsOneWidget,
);
expect(find.byGlyph(AppGlyphs.checkCircle), findsNothing);
expect(find.byGlyph(AppGlyphs.error), findsNothing);
await tester.pumpWidget(const SizedBox.shrink());
});
testWidgets('a rejected manual request clears progress', (tester) async {
final coreInterface = _MockCoreHandlerInterface();
when(
() => coreInterface.updateGeoData('MMDB'),
).thenAnswer((_) async => 'unknown geo resource');
final container = await _pumpGeoResourceAction(tester, coreInterface);
final action = container.read(geoResourceActionProvider.notifier);
final key = GeoResource.MMDB.updatingKey;
await expectLater(
action.updateGeoResource(GeoResource.MMDB),
throwsA(
isA<MessageException>().having(
(error) => error.message,
'message',
'unknown geo resource',
),
),
);
expect(container.read(isUpdatingProvider(key)), isFalse);
await tester.pumpWidget(const SizedBox.shrink());
});
testWidgets('a repeated manual request stays one operation', (tester) async {
final coreInterface = _MockCoreHandlerInterface();
when(() => coreInterface.updateGeoData('MMDB')).thenAnswer((_) async => '');
final container = await _pumpGeoResourceAction(tester, coreInterface);
final action = container.read(geoResourceActionProvider.notifier);
final key = GeoResource.MMDB.updatingKey;
await action.updateGeoResource(GeoResource.MMDB);
action.handleCoreUpdate('MMDB', true, false, null);
await action.updateGeoResource(GeoResource.MMDB);
expect(container.read(isUpdatingProvider(key)), isTrue);
action.handleCoreUpdate('MMDB', false, false, null);
await tester.pump();
expect(container.read(isUpdatingProvider(key)), isFalse);
await tester.pumpWidget(const SizedBox.shrink());
});
testWidgets('a core disconnect drops the pending geo operation', (
tester,
) async {
final coreInterface = _MockCoreHandlerInterface();
when(() => coreInterface.updateGeoData('MMDB')).thenAnswer((_) async => '');
final container = await _pumpGeoResourceAction(tester, coreInterface);
container.read(coreStatusProvider.notifier).value = CoreStatus.connected;
final action = container.read(geoResourceActionProvider.notifier);
final key = GeoResource.MMDB.updatingKey;
await action.updateGeoResource(GeoResource.MMDB);
expect(container.read(isUpdatingProvider(key)), isTrue);
container.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
await tester.pump();
expect(container.read(isUpdatingProvider(key)), isFalse);
action.handleCoreUpdate('MMDB', false, false, null);
await tester.pump();
expect(container.read(isUpdatingProvider(key)), isFalse);
await tester.pumpWidget(const SizedBox.shrink());
});
testWidgets('the stale sweep preserves late manual completion', (
tester,
) async {
final coreInterface = _MockCoreHandlerInterface();
when(() => coreInterface.updateGeoData('MMDB')).thenAnswer((_) async => '');
final container = await _pumpGeoResourceAction(tester, coreInterface);
container.read(updatingActionProvider.notifier);
final action = container.read(geoResourceActionProvider.notifier);
final key = GeoResource.MMDB.updatingKey;
await action.updateGeoResource(GeoResource.MMDB);
await tester.pump(updatingStaleTimeout + updatingSweepInterval);
expect(container.read(isUpdatingProvider(key)), isFalse);
action.handleCoreUpdate('MMDB', false, false, null);
await tester.pump();
expect(
find.text(currentAppLocalizations.geoUpdated(GeoResource.MMDB.name)),
findsOneWidget,
);
await tester.pumpWidget(const SizedBox.shrink());
});
testWidgets('a manual update failure is surfaced as an error', (
tester,
) async {
final coreInterface = _MockCoreHandlerInterface();
when(() => coreInterface.updateGeoData('MMDB')).thenAnswer((_) async => '');
final container = await _pumpGeoResourceAction(tester, coreInterface);
final action = container.read(geoResourceActionProvider.notifier);
await action.updateGeoResource(GeoResource.MMDB);
action.handleCoreUpdate('MMDB', false, false, 'download failed');
await tester.pump();
expect(find.text('download failed'), findsOneWidget);
expect(find.byGlyph(AppGlyphs.error), findsOneWidget);
await tester.pumpWidget(const SizedBox.shrink());
});
testWidgets('leaving the connected Core clears manual feedback', (
tester,
) async {
final coreInterface = _MockCoreHandlerInterface();
when(() => coreInterface.updateGeoData('MMDB')).thenAnswer((_) async => '');
final container = await _pumpGeoResourceAction(tester, coreInterface);
container.read(coreStatusProvider.notifier).value = CoreStatus.connected;
final action = container.read(geoResourceActionProvider.notifier);
await action.updateGeoResource(GeoResource.MMDB);
container.read(coreStatusProvider.notifier).value = CoreStatus.connecting;
action.handleCoreUpdate('MMDB', false, false, null);
await tester.pump();
expect(
find.text(currentAppLocalizations.geoUpdated(GeoResource.MMDB.name)),
findsNothing,
);
await tester.pumpWidget(const SizedBox.shrink());
});
}