mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-13 20:03:46 +00:00
Merge remote-tracking branch 'origin/main' into codex/fix-bt-file-selection
# Conflicts: # ui/flutter/test/features/tasks/domain/task_record_test.dart
This commit is contained in:
commit
a83e3a2b84
272 files changed
+17212
-1065
No files matched your search
+79
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:gopeed/api/model/extension.dart';
|
||||
import 'package:gopeed/features/extensions/presentation/widgets/extension_setting_field.dart';
|
||||
import 'package:gopeed/shared/widgets/app_text_field.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> pumpField(WidgetTester tester, Setting setting, TextEditingController controller) async {
|
||||
await tester.pumpWidget(
|
||||
ShadcnApp(
|
||||
home: Scaffold(
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 280,
|
||||
child: ExtensionSettingField(setting: setting, controller: controller),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Setting setting(SettingType type) =>
|
||||
Setting(name: 'test', title: 'Test setting', description: 'Description', required: false, type: type);
|
||||
|
||||
testWidgets('boolean switch loads and updates the saved value', (tester) async {
|
||||
final controller = TextEditingController(text: 'true');
|
||||
addTearDown(controller.dispose);
|
||||
await pumpField(tester, setting(SettingType.boolean), controller);
|
||||
expect(find.byType(AppTextField), findsNothing);
|
||||
expect(tester.widget<Switch>(find.byType(Switch)).value, isTrue);
|
||||
await tester.tap(find.byType(Switch));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.text, 'false');
|
||||
expect(tester.widget<Switch>(find.byType(Switch)).value, isFalse);
|
||||
});
|
||||
|
||||
for (final type in [SettingType.string, SettingType.number]) {
|
||||
testWidgets(
|
||||
'$type options show labels and save selected values',
|
||||
(tester) async {
|
||||
final field = setting(type)..options = [Option(label: 'First', value: 1), Option(label: 'Second', value: 2)];
|
||||
final controller = TextEditingController(text: '1');
|
||||
addTearDown(controller.dispose);
|
||||
await pumpField(tester, field, controller);
|
||||
expect(find.byType(AppTextField), findsNothing);
|
||||
expect(find.text('First'), findsOneWidget);
|
||||
await tester.tap(find.byType(Select<String>));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.tap(find.text('Second'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.text, '2');
|
||||
expect(find.text('Second'), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
variant: TargetPlatformVariant({TargetPlatform.android, TargetPlatform.macOS}),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('number field accepts decimals and rejects nonnumeric input', (tester) async {
|
||||
final controller = TextEditingController();
|
||||
addTearDown(controller.dispose);
|
||||
await pumpField(tester, setting(SettingType.number), controller);
|
||||
await tester.enterText(find.byType(AppTextField), '-12.5');
|
||||
expect(controller.text, '-12.5');
|
||||
await tester.enterText(find.byType(AppTextField), 'invalid');
|
||||
expect(controller.text, '-12.5');
|
||||
});
|
||||
|
||||
testWidgets('string with empty options remains a text field', (tester) async {
|
||||
final controller = TextEditingController(text: 'original');
|
||||
addTearDown(controller.dispose);
|
||||
await pumpField(tester, setting(SettingType.string)..options = [], controller);
|
||||
await tester.enterText(find.byType(AppTextField), 'updated');
|
||||
expect(controller.text, 'updated');
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gopeed/api/model/task.dart' as api;
|
||||
import 'package:gopeed/core/icons/gopeed_icons.dart';
|
||||
import 'package:gopeed/features/tasks/domain/task_record.dart';
|
||||
|
||||
void main() {
|
||||
@@ -41,4 +42,19 @@ void main() {
|
||||
expect(record.files.length, 3);
|
||||
expect(record.totalBytes, 60);
|
||||
});
|
||||
|
||||
test('file names preserve hash and question mark characters for type detection', () {
|
||||
expect(
|
||||
taskFileTypeIcon('Slow English Podcast #2 _ Eating, Talking, and Learning Natural English.mp4'),
|
||||
GopeedIcons.fileVideo,
|
||||
);
|
||||
expect(taskFileTypeIcon('What is this? #2.MP4'), GopeedIcons.fileVideo);
|
||||
expect(taskFileTypeIcon('Episode #2.mp3'), GopeedIcons.fileAudio);
|
||||
});
|
||||
|
||||
test('URL fallback ignores query and fragment when detecting file type', () {
|
||||
expect(taskFileTypeIcon('https://example.com/video.mp4?token=abc#part'), GopeedIcons.fileVideo);
|
||||
expect(taskFileTypeIcon('https://example.com/download?name=video.mp4'), GopeedIcons.file);
|
||||
expect(taskFileTypeIcon('Playlist #2.mp4', isFolder: true), GopeedIcons.folder);
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter/widgets.dart' as flutter show Row;
|
||||
import 'package:flutter/foundation.dart' show TargetPlatform, debugDefaultTargetPlatformOverride;
|
||||
import 'package:flutter/material.dart' show Icons;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -8,19 +11,197 @@ import 'package:gopeed/api/model/create_task.dart';
|
||||
import 'package:gopeed/api/model/downloader_config.dart';
|
||||
import 'package:gopeed/api/model/options.dart';
|
||||
import 'package:gopeed/api/model/request.dart';
|
||||
import 'package:gopeed/api/model/resolve_result.dart';
|
||||
import 'package:gopeed/api/model/resource.dart';
|
||||
import 'package:gopeed/core/capabilities/app_capabilities.dart';
|
||||
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/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';
|
||||
import 'package:gopeed/shared/theme/app_theme.dart';
|
||||
import 'package:gopeed/shared/widgets/app_loading_button.dart';
|
||||
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 {
|
||||
tester.view.physicalSize = const Size(700, 900);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
CreateTask? submitted;
|
||||
final registry = CapabilityRegistry(createAppCapabilityCodecs())
|
||||
..bind(GopeedMethods.getConfig, (_) => DownloaderConfig(downloadDir: '/downloads'))
|
||||
..bind(GopeedMethods.createTask, (task) {
|
||||
submitted = task;
|
||||
return 'created-id';
|
||||
})
|
||||
..bind(StorageMethods.saveCreateHistory, (_) => const RpcUnit());
|
||||
final container = ProviderContainer(
|
||||
overrides: [appCapabilitiesProvider.overrideWithValue(AppCapabilities(LocalCapabilityInvoker(registry)))],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
final pending = container.read(pendingCreateTaskProvider.notifier);
|
||||
pending.set(
|
||||
CreateTask(
|
||||
req: Request(
|
||||
url: 'https://example.com/first.zip',
|
||||
rawUrl: 'https://example.com/first',
|
||||
labels: {'source': 'first'},
|
||||
extra: ReqExtraHttp(method: 'POST', body: 'old-body', header: {'Cookie': 'old-cookie'}).toJson(),
|
||||
proxy: RequestProxy(mode: RequestProxyMode.custom, host: 'localhost:8080'),
|
||||
skipVerifyCert: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
final router = GoRouter(
|
||||
initialLocation: '/create',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (_, _) => const SizedBox()),
|
||||
GoRoute(path: '/create', builder: (_, _) => const CreateTaskWindowPage()),
|
||||
],
|
||||
);
|
||||
addTearDown(router.dispose);
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: shad.ShadcnApp.router(
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
routerConfig: router,
|
||||
builder: (_, child) => AppComponentThemes(child: child!),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(_fieldText(tester, 'create-task-url-input'), 'https://example.com/first.zip');
|
||||
expect(container.read(pendingCreateTaskProvider), isNull);
|
||||
final pageState = tester.state(find.byType(CreateTaskWindowPage));
|
||||
|
||||
for (final url in [
|
||||
'https://example.com/second.zip',
|
||||
'https://example.com/third.zip',
|
||||
'https://example.com/third.zip',
|
||||
]) {
|
||||
pending.set(CreateTask(req: Request(url: url)));
|
||||
router.go('/create');
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.state(find.byType(CreateTaskWindowPage)), same(pageState));
|
||||
expect(_fieldText(tester, 'create-task-url-input'), url);
|
||||
expect(container.read(pendingCreateTaskProvider), isNull);
|
||||
}
|
||||
await tester.tap(find.byKey(const ValueKey('create-task-direct-download-toggle')));
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(submitted?.req?.url, 'https://example.com/third.zip');
|
||||
expect(submitted?.req?.rawUrl, isNull);
|
||||
expect(submitted?.req?.labels, isNull);
|
||||
expect(submitted?.req?.extra, isNull);
|
||||
expect(submitted?.req?.proxy?.mode, RequestProxyMode.follow);
|
||||
expect(submitted?.req?.skipVerifyCert, isFalse);
|
||||
expect(tester.takeException(), isNull);
|
||||
} finally {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
tester.view.padding = const FakeViewPadding(top: 32, bottom: 24);
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
addTearDown(tester.view.resetPadding);
|
||||
|
||||
final registry = CapabilityRegistry(createAppCapabilityCodecs())
|
||||
..bind(GopeedMethods.getConfig, (_) => DownloaderConfig(downloadDir: '/downloads'));
|
||||
final capabilities = AppCapabilities(LocalCapabilityInvoker(registry));
|
||||
|
||||
try {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [appCapabilitiesProvider.overrideWithValue(capabilities)],
|
||||
child: shad.ShadcnApp(
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
home: AppComponentThemes(child: const CreateTaskWindowPage()),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('browser extension request parameters populate the form and survive submission', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
@@ -121,7 +302,10 @@ void main() {
|
||||
|
||||
final connectionsInput = find.byKey(const ValueKey('create-task-connections-input'));
|
||||
expect(connectionsInput, findsOneWidget);
|
||||
expect(tester.getSize(connectionsInput).width, AppDesignTokens.settingsNumberControlWidth);
|
||||
expect(
|
||||
tester.getSize(connectionsInput).width,
|
||||
tester.getSize(find.byKey(const ValueKey('create-task-rename-input'))).width,
|
||||
);
|
||||
final connectionsField = tester.widget<shad.TextField>(connectionsInput);
|
||||
expect(connectionsField.controller!.text, '12');
|
||||
expect(connectionsField.features.single, isA<shad.InputSpinnerFeature>());
|
||||
@@ -280,6 +464,58 @@ void main() {
|
||||
expect(submitted?.opts?.asDefaultPath, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('resolved task keeps rename empty and uses equal-width single-line actions', (WidgetTester tester) async {
|
||||
tester.view.physicalSize = const Size(700, 700);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
final registry = CapabilityRegistry(createAppCapabilityCodecs())
|
||||
..bind(GopeedMethods.getConfig, (_) => DownloaderConfig(downloadDir: '/downloads'))
|
||||
..bind(
|
||||
GopeedMethods.resolve,
|
||||
(_) => ResolveResult(
|
||||
id: 'resolved-id',
|
||||
res: Resource(
|
||||
name: 'server-generated-name',
|
||||
files: [FileInfo(name: 'video.mp4', size: 1024)],
|
||||
),
|
||||
),
|
||||
)
|
||||
..bind(StorageMethods.saveCreateHistory, (_) => const RpcUnit());
|
||||
final capabilities = AppCapabilities(LocalCapabilityInvoker(registry));
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [appCapabilitiesProvider.overrideWithValue(capabilities)],
|
||||
child: shad.ShadcnApp(
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
home: AppComponentThemes(child: const CreateTaskWindowPage()),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(find.byKey(const ValueKey('create-task-url-input')), 'https://example.com/video.mp4');
|
||||
await tester.tap(find.byKey(const ValueKey('create-task-confirm-button')));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
|
||||
expect(_fieldText(tester, 'create-task-rename-input'), isEmpty);
|
||||
final createButton = tester.widget<AppLoadingButton>(find.byKey(const ValueKey('resolve-create-button')));
|
||||
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'))),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('create history supports filtering, deleting one entry, and clearing all entries', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
@@ -324,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,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gopeed/features/tasks/presentation/widgets/history_search_field.dart';
|
||||
import 'package:gopeed/l10n/l10n.dart';
|
||||
import 'package:gopeed/shared/theme/app_component_themes.dart';
|
||||
import 'package:gopeed/shared/theme/app_theme.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
||||
|
||||
void main() {
|
||||
for (final platform in [TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.macOS]) {
|
||||
for (final scale in [1.0, 1.5, 2.0]) {
|
||||
testWidgets('history hint and input line align on $platform at scale $scale', (tester) async {
|
||||
debugDefaultTargetPlatformOverride = platform;
|
||||
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
||||
final controller = TextEditingController();
|
||||
addTearDown(controller.dispose);
|
||||
String? query;
|
||||
for (final width in [280.0, 360.0, 600.0]) {
|
||||
await tester.pumpWidget(
|
||||
shad.ShadcnApp(
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: AppComponentThemes(
|
||||
child: Builder(
|
||||
builder: (context) => Localizations.override(
|
||||
context: context,
|
||||
locale: const Locale('zh'),
|
||||
child: MediaQuery(
|
||||
data: MediaQueryData(textScaler: TextScaler.linear(scale)),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: width,
|
||||
child: HistorySearchField(controller: controller, onChanged: (value) => query = value),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final editable = tester.state<EditableTextState>(find.byType(EditableText)).renderEditable;
|
||||
final hint = tester.renderObject<RenderBox>(find.text('搜索历史记录…'));
|
||||
expect(editable.preferredLineHeight, lessThanOrEqualTo(editable.size.height));
|
||||
expect(hint.size.height, closeTo(editable.preferredLineHeight, 0.1));
|
||||
expect(hint.localToGlobal(Offset.zero).dy, closeTo(editable.localToGlobal(Offset.zero).dy, 0.1));
|
||||
expect(tester.takeException(), isNull);
|
||||
}
|
||||
await tester.enterText(find.byType(EditableText), 'example');
|
||||
expect(query, 'example');
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.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';
|
||||
import 'package:gopeed/shared/widgets/app_loading_button.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
||||
|
||||
void main() {
|
||||
for (final width in [390.0, 1024.0]) {
|
||||
for (final language in ['en', 'zh', 'de']) {
|
||||
testWidgets('resolve actions stay equal at $width in $language, including loading', (tester) async {
|
||||
tester.view.physicalSize = Size(width, 760);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
var submitting = false;
|
||||
var cancelled = false;
|
||||
await tester.pumpWidget(
|
||||
shad.ShadcnApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
home: Builder(
|
||||
builder: (context) => Localizations.override(
|
||||
context: context,
|
||||
locale: Locale(language),
|
||||
delegates: const [AppLocalizations.delegate],
|
||||
child: AppComponentThemes(
|
||||
child: Center(
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) => shad.AlertDialog(
|
||||
padding: const EdgeInsets.all(18),
|
||||
content: SizedBox(width: width < 760 ? width - 32 : 720, height: 100),
|
||||
actions: [
|
||||
Flexible(
|
||||
child: CreateTaskActionButtons(
|
||||
submitting: submitting,
|
||||
onCancel: () => cancelled = 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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final cancel = find.byKey(const ValueKey('resolve-cancel-button'));
|
||||
final create = find.byKey(const ValueKey('resolve-create-button'));
|
||||
final cancelRect = tester.getRect(cancel);
|
||||
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);
|
||||
await tester.tap(cancel);
|
||||
expect(cancelled, isTrue);
|
||||
await tester.tap(create);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
expect(tester.widget<AppLoadingButton>(create).loading, isTrue);
|
||||
expect(tester.widget<shad.SecondaryButton>(cancel).onPressed, isNull);
|
||||
expect(tester.getRect(cancel), cancelRect);
|
||||
expect(tester.getRect(create), createRect);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user