mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-12 19:33:41 +00:00
Merge latest main into 2.0.0-beta.2
This commit is contained in:
commit
ddce649c8b
74 files changed
+431
-176
No files matched your search
@@ -18,8 +18,8 @@ import 'package:gopeed/core/capabilities/app_navigation_capability.dart';
|
||||
import 'package:gopeed/core/capabilities/capability_rpc.dart';
|
||||
import 'package:gopeed/core/capabilities/gopeed_capability.dart';
|
||||
import 'package:gopeed/core/capabilities/storage_capability.dart';
|
||||
import 'package:gopeed/features/tasks/application/pending_create_task.dart';
|
||||
import 'package:gopeed/features/tasks/presentation/pages/create_task_window_page.dart';
|
||||
import 'package:gopeed/features/tasks/application/pending_create_task.dart';
|
||||
import 'package:gopeed/shared/services/download_directory_picker.dart';
|
||||
import 'package:gopeed/shared/theme/app_component_themes.dart';
|
||||
import 'package:gopeed/shared/theme/app_design_tokens.dart';
|
||||
@@ -29,6 +29,58 @@ import 'package:gopeed/shared/widgets/app_tooltip.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
||||
|
||||
void main() {
|
||||
for (final navigationFails in [false, true]) {
|
||||
testWidgets('child creation selects downloads before closing (navigation fails: $navigationFails)', (tester) async {
|
||||
tester.view.physicalSize = const Size(1024, 720);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final events = <String>[];
|
||||
const channel = MethodChannel('window_manager');
|
||||
final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||
messenger.setMockMethodCallHandler(channel, (call) async {
|
||||
events.add(call.method);
|
||||
return null;
|
||||
});
|
||||
addTearDown(() => messenger.setMockMethodCallHandler(channel, null));
|
||||
final config = DownloaderConfig(downloadDir: '/downloads')..extra.defaultDirectDownload = true;
|
||||
final registry = CapabilityRegistry(createAppCapabilityCodecs())
|
||||
..bind(GopeedMethods.getConfig, (_) => config)
|
||||
..bind(StorageMethods.saveCreateHistory, (_) => const RpcUnit())
|
||||
..bind(GopeedMethods.createTask, (_) {
|
||||
events.add('created');
|
||||
return 'task-id';
|
||||
})
|
||||
..bind(NavigationMethods.showDownloadingTasks, (_) {
|
||||
events.add('downloads');
|
||||
if (navigationFails) throw StateError('Navigation unavailable');
|
||||
return const RpcUnit();
|
||||
});
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [appCapabilitiesProvider.overrideWithValue(AppCapabilities(LocalCapabilityInvoker(registry)))],
|
||||
child: shad.ShadcnApp(
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
home: AppComponentThemes(
|
||||
child: CreateTaskWindowPage(
|
||||
windowController: WindowController.fromWindowId('child'),
|
||||
initialTask: CreateTask(req: Request(url: 'https://example.com/file.apk')),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Confirm'));
|
||||
// The mocked native close deliberately leaves the widget mounted.
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
expect(events, ['created', 'downloads', 'close']);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets('repeated external tasks update the open create route and replace request metadata', (tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
try {
|
||||
@@ -113,59 +165,7 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
for (final navigationFails in [false, true]) {
|
||||
testWidgets('child creation selects downloads before closing (navigation fails: $navigationFails)', (tester) async {
|
||||
tester.view.physicalSize = const Size(1024, 720);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final events = <String>[];
|
||||
const channel = MethodChannel('window_manager');
|
||||
final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||
messenger.setMockMethodCallHandler(channel, (call) async {
|
||||
events.add(call.method);
|
||||
return null;
|
||||
});
|
||||
addTearDown(() => messenger.setMockMethodCallHandler(channel, null));
|
||||
final config = DownloaderConfig(downloadDir: '/downloads')..extra.defaultDirectDownload = true;
|
||||
final registry = CapabilityRegistry(createAppCapabilityCodecs())
|
||||
..bind(GopeedMethods.getConfig, (_) => config)
|
||||
..bind(StorageMethods.saveCreateHistory, (_) => const RpcUnit())
|
||||
..bind(GopeedMethods.createTask, (_) {
|
||||
events.add('created');
|
||||
return 'task-id';
|
||||
})
|
||||
..bind(NavigationMethods.showDownloadingTasks, (_) {
|
||||
events.add('downloads');
|
||||
if (navigationFails) throw StateError('Navigation unavailable');
|
||||
return const RpcUnit();
|
||||
});
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [appCapabilitiesProvider.overrideWithValue(AppCapabilities(LocalCapabilityInvoker(registry)))],
|
||||
child: shad.ShadcnApp(
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
home: AppComponentThemes(
|
||||
child: CreateTaskWindowPage(
|
||||
windowController: WindowController.fromWindowId('child'),
|
||||
initialTask: CreateTask(req: Request(url: 'https://example.com/file.apk')),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Confirm'));
|
||||
// The mocked native close deliberately leaves the widget mounted.
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
expect(events, ['created', 'downloads', 'close']);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets('mobile create task page starts below the system status bar', (WidgetTester tester) async {
|
||||
testWidgets('mobile create task page stays within the system safe areas', (WidgetTester tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
tester.view.physicalSize = const Size(700, 760);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
@@ -193,6 +193,10 @@ void main() {
|
||||
|
||||
final contentPadding = tester.widget<Padding>(find.byKey(const ValueKey('create-task-safe-content')));
|
||||
expect((contentPadding.padding as EdgeInsets).top, 32);
|
||||
expect(
|
||||
tester.getBottomRight(find.byKey(const ValueKey('create-task-confirm-button'))).dy,
|
||||
lessThanOrEqualTo(tester.view.physicalSize.height / tester.view.devicePixelRatio - 24),
|
||||
);
|
||||
} finally {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
}
|
||||
@@ -458,7 +462,6 @@ void main() {
|
||||
expect(options.archivePassword, 'archive-password');
|
||||
expect(options.deleteAfterExtract, isTrue);
|
||||
expect(submitted?.opts?.asDefaultPath, isTrue);
|
||||
expect(submitted?.opts?.name, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('resolved task keeps rename empty and uses equal-width single-line actions', (WidgetTester tester) async {
|
||||
@@ -500,11 +503,12 @@ void main() {
|
||||
|
||||
expect(_fieldText(tester, 'create-task-rename-input'), isEmpty);
|
||||
final createButton = tester.widget<AppLoadingButton>(find.byKey(const ValueKey('resolve-create-button')));
|
||||
expect(createButton.child, isA<Text>());
|
||||
final createText = createButton.child as Text;
|
||||
expect(createText.data, 'Create');
|
||||
expect(createText.maxLines, 1);
|
||||
expect(createText.softWrap, isFalse);
|
||||
expect(createButton.child, isA<SizedBox>());
|
||||
expect((createButton.child as SizedBox).width, 68);
|
||||
expect(
|
||||
find.descendant(of: find.byKey(const ValueKey('resolve-create-button')), matching: find.text('Create')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const ValueKey('resolve-cancel-button'))),
|
||||
tester.getSize(find.byKey(const ValueKey('resolve-create-button'))),
|
||||
@@ -556,6 +560,7 @@ void main() {
|
||||
|
||||
await tester.tap(find.byIcon(Icons.history));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('History Links'), findsOneWidget);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const ValueKey('create-history-title-bar'))).width,
|
||||
tester.getSize(find.byKey(const ValueKey('create-history-content'))).width,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gopeed/features/tasks/presentation/widgets/resolve_task_actions.dart';
|
||||
import 'package:gopeed/features/tasks/presentation/widgets/create_task_action_buttons.dart';
|
||||
import 'package:gopeed/l10n/l10n.dart';
|
||||
import 'package:gopeed/shared/theme/app_component_themes.dart';
|
||||
import 'package:gopeed/shared/theme/app_theme.dart';
|
||||
@@ -36,10 +36,14 @@ void main() {
|
||||
content: SizedBox(width: width < 760 ? width - 32 : 720, height: 100),
|
||||
actions: [
|
||||
Flexible(
|
||||
child: ResolveTaskActions(
|
||||
child: CreateTaskActionButtons(
|
||||
submitting: submitting,
|
||||
onCancel: () => cancelled = true,
|
||||
onCreate: () => setState(() => submitting = true),
|
||||
onSubmit: () => setState(() => submitting = true),
|
||||
cancelLabel: context.l10n.cancel,
|
||||
submitLabel: context.l10n.createAction,
|
||||
cancelButtonKey: const ValueKey('resolve-cancel-button'),
|
||||
submitButtonKey: const ValueKey('resolve-create-button'),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -58,6 +62,9 @@ void main() {
|
||||
final createRect = tester.getRect(create);
|
||||
expect(cancelRect.size, createRect.size);
|
||||
expect(cancelRect.center.dy, createRect.center.dy);
|
||||
expect(createRect.left - cancelRect.right, 12);
|
||||
expect((tester.widget<shad.SecondaryButton>(cancel).child as SizedBox).width, 68);
|
||||
expect((tester.widget<AppLoadingButton>(create).child as SizedBox).width, 68);
|
||||
expect(cancelRect.left, greaterThanOrEqualTo(0));
|
||||
expect(createRect.right, lessThanOrEqualTo(width));
|
||||
expect(find.text(appLocalizationsFor(language).createAction), findsOneWidget);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:flutter/material.dart' show Icons;
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gopeed/api/model/downloader_config.dart';
|
||||
import 'package:gopeed/features/settings/presentation/widgets/app_update_dialog.dart';
|
||||
import 'package:gopeed/features/settings/presentation/widgets/app_update_notes_viewport.dart';
|
||||
import 'package:gopeed/shared/theme/app_theme.dart';
|
||||
import 'package:gopeed/util/arch/arch.dart';
|
||||
import 'package:gopeed/util/github_mirror.dart';
|
||||
@@ -193,10 +195,47 @@ void main() {
|
||||
expect(find.text('下载更快'), findsNothing);
|
||||
expect(find.text('Later'), findsOneWidget);
|
||||
expect(find.text('Update Now'), findsOneWidget);
|
||||
expect(find.text("What's New"), findsNothing);
|
||||
final versionText = tester.widget<Text>(find.text('2.0.0-beta.1 → 2.0.0-beta.2'));
|
||||
expect(versionText.style?.decoration, TextDecoration.none);
|
||||
expect(versionText.style?.inherit, isFalse);
|
||||
expect(find.byIcon(Icons.system_update_alt_outlined), findsNothing);
|
||||
final dialog = tester.getRect(find.byKey(const ValueKey('app-update-dialog')));
|
||||
expect(dialog.width, lessThan(320));
|
||||
expect(tester.getCenter(find.byKey(const ValueKey('app-update-heading'))).dx, closeTo(dialog.center.dx, 0.01));
|
||||
expect(tester.getCenter(find.byKey(const ValueKey('app-update-actions'))).dx, closeTo(dialog.center.dx, 0.01));
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('release notes keep the last line above the fade when scrolled to the end', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
shad.ShadcnApp(
|
||||
theme: AppTheme.dark(),
|
||||
materialTheme: AppTheme.materialDark(),
|
||||
home: Center(
|
||||
child: SizedBox(
|
||||
width: 280,
|
||||
child: AppUpdateNotesViewport(
|
||||
child: Column(children: [for (var i = 0; i < 30; i++) Text('Release note $i')]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final scrollbar = tester.widget<shad.Scrollbar>(find.byType(shad.Scrollbar));
|
||||
expect(scrollbar.thumbVisibility, isTrue);
|
||||
final controller = scrollbar.controller!;
|
||||
expect(controller.position.maxScrollExtent, greaterThan(0));
|
||||
controller.jumpTo(controller.position.maxScrollExtent);
|
||||
await tester.pumpAndSettle();
|
||||
final viewport = tester.getRect(find.byType(AppUpdateNotesViewport));
|
||||
final lastLine = tester.getRect(find.text('Release note 29'));
|
||||
expect(lastLine.bottom, lessThanOrEqualTo(viewport.bottom - 24));
|
||||
expect(lastLine.top, greaterThan(viewport.top));
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
@@ -2669,9 +2669,9 @@ void main() {
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ResolveFileTree(
|
||||
files: [
|
||||
FileInfo(path: 'videos', name: 'clip.mp4', size: 1024),
|
||||
FileInfo(path: 'docs', name: 'notes.txt', size: 128),
|
||||
FileInfo(path: 'docs', name: 'manual.pdf', size: 256),
|
||||
FileInfo(path: 'videos', name: 'clip.mp4', size: 0),
|
||||
FileInfo(path: 'docs', name: 'notes.txt', size: 0),
|
||||
FileInfo(path: 'docs', name: 'manual.pdf', size: 0),
|
||||
],
|
||||
initialSelection: const [0, 1, 2],
|
||||
onSelectionChanged: (values) => selected = values,
|
||||
@@ -2708,7 +2708,26 @@ void main() {
|
||||
expect(find.byKey(const ValueKey('resolve-tree-expand-toggle')), findsOneWidget);
|
||||
expect(find.byType(shad.OutlinedContainer), findsOneWidget);
|
||||
expect(find.text('clip.mp4'), findsOneWidget);
|
||||
expect(find.text('Unknown size'), findsOneWidget);
|
||||
expect(tester.widget<Text>(find.text('clip.mp4')).style?.color, AppPalette.light.textPrimary);
|
||||
final baselines = tester
|
||||
.widgetList<Baseline>(
|
||||
find.descendant(
|
||||
of: find.byKey(const ValueKey('resolve-tree-selection-stats')),
|
||||
matching: find.byType(Baseline),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
expect(baselines, hasLength(2));
|
||||
expect(baselines.first.baseline, baselines.last.baseline);
|
||||
expect(baselines.every((widget) => widget.baselineType == TextBaseline.alphabetic), isTrue);
|
||||
final filtersRect = tester.getRect(find.byKey(const ValueKey('resolve-tree-filters')));
|
||||
final statsRect = tester.getRect(find.byKey(const ValueKey('resolve-tree-selection-stats')));
|
||||
final selectedCountRect = tester.getRect(find.byKey(const ValueKey('resolve-tree-selected-count')));
|
||||
final selectedSizeRect = tester.getRect(find.byKey(const ValueKey('resolve-tree-selected-size')));
|
||||
expect(statsRect.center.dy, closeTo(filtersRect.center.dy, 0.01));
|
||||
expect(selectedCountRect.center.dy, closeTo(selectedSizeRect.center.dy, 0.01));
|
||||
expect(selectedSizeRect.left, greaterThan(selectedCountRect.right));
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('resolve-tree-expand-toggle')));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -2770,8 +2789,16 @@ void main() {
|
||||
expect(find.text('clip.mp4'), findsOneWidget);
|
||||
expect(selected, [0, 1, 2]);
|
||||
|
||||
tester.view.physicalSize = const Size(360, 620);
|
||||
tester.view.physicalSize = const Size(280, 620);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
tester.getRect(find.byKey(const ValueKey('resolve-tree-selection-stats'))).top,
|
||||
greaterThanOrEqualTo(tester.getRect(find.byKey(const ValueKey('resolve-tree-filters'))).bottom),
|
||||
);
|
||||
expect(
|
||||
tester.getRect(find.byKey(const ValueKey('resolve-tree-selected-size'))).top,
|
||||
greaterThanOrEqualTo(tester.getRect(find.byKey(const ValueKey('resolve-tree-selected-count'))).bottom),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user