mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 08:10:55 +00:00
142 lines
5.6 KiB
Dart
142 lines
5.6 KiB
Dart
import 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart' as material show AdaptiveTextSelectionToolbar, Icons;
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:gopeed/shared/theme/app_component_themes.dart';
|
|
import 'package:gopeed/shared/theme/app_theme.dart';
|
|
import 'package:gopeed/shared/widgets/app_search_field.dart';
|
|
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
|
|
|
void main() {
|
|
for (final direction in TextDirection.values) {
|
|
for (final width in [240.0, 480.0]) {
|
|
testWidgets(
|
|
'scrolling and selecting long search text preserves the icon ($direction, $width)',
|
|
(tester) async {
|
|
final controller = TextEditingController();
|
|
final focus = FocusNode();
|
|
addTearDown(controller.dispose);
|
|
addTearDown(focus.dispose);
|
|
await _pumpField(tester, controller, focus, width: width, direction: direction);
|
|
focus.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
final iconBefore = await _iconPixels(tester);
|
|
final iconRect = tester.getRect(find.byIcon(material.Icons.search_rounded));
|
|
final text = List.filled(
|
|
20,
|
|
direction == TextDirection.ltr ? 'long-search-text-' : 'نص البحث الطويل ',
|
|
).join();
|
|
controller.value = TextEditingValue(
|
|
text: text,
|
|
selection: TextSelection.collapsed(offset: text.length),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final editable = tester.state<EditableTextState>(find.byType(EditableText));
|
|
expect(editable.renderEditable.maxScrollExtent, greaterThan(0));
|
|
editable.renderEditable.offset.jumpTo(editable.renderEditable.maxScrollExtent);
|
|
await tester.pumpAndSettle();
|
|
expect(editable.renderEditable.offset.pixels, greaterThan(0));
|
|
expect(tester.getRect(find.byIcon(material.Icons.search_rounded)), iconRect);
|
|
expect(await _iconPixels(tester), orderedEquals(iconBefore));
|
|
controller.selection = TextSelection(baseOffset: 0, extentOffset: text.length);
|
|
await tester.pumpAndSettle();
|
|
expect(await _iconPixels(tester), orderedEquals(iconBefore));
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
variant: TargetPlatformVariant({TargetPlatform.android, TargetPlatform.macOS}),
|
|
);
|
|
}
|
|
}
|
|
|
|
testWidgets(
|
|
'search text keeps Android native selection actions',
|
|
(tester) async {
|
|
final controller = TextEditingController(text: 'search selection');
|
|
final focus = FocusNode();
|
|
addTearDown(controller.dispose);
|
|
addTearDown(focus.dispose);
|
|
await _pumpField(tester, controller, focus);
|
|
await tester.longPress(find.byType(EditableText));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(material.AdaptiveTextSelectionToolbar), findsOneWidget);
|
|
expect(find.byType(shad.MobileEditableTextContextMenu), findsNothing);
|
|
},
|
|
skip: kIsWeb,
|
|
variant: TargetPlatformVariant({TargetPlatform.android}),
|
|
);
|
|
|
|
testWidgets(
|
|
'search text keeps desktop native selection actions',
|
|
(tester) async {
|
|
final controller = TextEditingController(text: 'search selection');
|
|
final focus = FocusNode();
|
|
addTearDown(controller.dispose);
|
|
addTearDown(focus.dispose);
|
|
await _pumpField(tester, controller, focus);
|
|
final gesture = await tester.startGesture(
|
|
tester.getCenter(find.byType(EditableText)),
|
|
kind: PointerDeviceKind.mouse,
|
|
buttons: kSecondaryMouseButton,
|
|
);
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(material.AdaptiveTextSelectionToolbar), findsOneWidget);
|
|
expect(find.byType(shad.DesktopEditableTextContextMenu), findsNothing);
|
|
},
|
|
skip: kIsWeb,
|
|
variant: TargetPlatformVariant({TargetPlatform.macOS}),
|
|
);
|
|
}
|
|
|
|
Future<void> _pumpField(
|
|
WidgetTester tester,
|
|
TextEditingController controller,
|
|
FocusNode focus, {
|
|
double width = 320,
|
|
TextDirection direction = TextDirection.ltr,
|
|
}) async {
|
|
await tester.pumpWidget(
|
|
shad.ShadcnApp(
|
|
theme: AppTheme.light(),
|
|
materialTheme: AppTheme.materialLight(),
|
|
home: AppComponentThemes(
|
|
child: Directionality(
|
|
textDirection: direction,
|
|
child: Center(
|
|
child: RepaintBoundary(
|
|
key: const ValueKey('search-paint-boundary'),
|
|
child: SizedBox(
|
|
width: width,
|
|
child: AppSearchField(controller: controller, focusNode: focus, placeholder: const Text('Search')),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
Future<List<int>> _iconPixels(WidgetTester tester) async {
|
|
final boundaryFinder = find.byKey(const ValueKey('search-paint-boundary'));
|
|
final boundary = tester.renderObject<RenderRepaintBoundary>(boundaryFinder);
|
|
final rect = tester.getRect(find.byIcon(material.Icons.search_rounded)).shift(-tester.getTopLeft(boundaryFinder));
|
|
final snapshot = await tester.runAsync(() async {
|
|
final image = await boundary.toImage(pixelRatio: 1);
|
|
final width = image.width;
|
|
final bytes = (await image.toByteData(format: ui.ImageByteFormat.rawRgba))!.buffer.asUint8List();
|
|
image.dispose();
|
|
return (width: width, pixels: Uint8List.fromList(bytes));
|
|
});
|
|
return [
|
|
for (var y = rect.top.floor(); y < rect.bottom.ceil(); y++)
|
|
for (var x = rect.left.floor(); x < rect.right.ceil(); x++)
|
|
...snapshot!.pixels.sublist((y * snapshot.width + x) * 4, (y * snapshot.width + x) * 4 + 4),
|
|
];
|
|
}
|