387 lines
12 KiB
Dart
387 lines
12 KiB
Dart
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:forui/forui.dart';
|
|
|
|
import 'package:pj_supermarket_cloud/common/index.dart';
|
|
|
|
/// 自研软键盘组件的渲染与输入联动测试。
|
|
void main() {
|
|
Widget wrapHost(Widget Function(BuildContext context) child) =>
|
|
ScreenUtilInit(
|
|
designSize: const Size(1920, 1080),
|
|
minTextAdapt: true,
|
|
splitScreenMode: true,
|
|
builder: (_, __) => MaterialApp(
|
|
home: FTheme(
|
|
data: FThemes.zinc.light,
|
|
child: Builder(builder: child),
|
|
),
|
|
),
|
|
);
|
|
|
|
testWidgets('弹出后渲染面板并可输入目标控制器', (tester) async {
|
|
final controller = TextEditingController();
|
|
tester.view.physicalSize = const Size(1920, 1080);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
wrapHost(
|
|
(context) => Scaffold(
|
|
body: Center(
|
|
child: FButton(
|
|
onPress: () => AppSoftKeyboard.show(
|
|
context,
|
|
controller: controller,
|
|
mode: AppSoftKeyboardMode.digits,
|
|
),
|
|
label: const Text('打开键盘'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('打开键盘'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// 面板、tab 与确认键都应渲染。
|
|
expect(find.text('确认'), findsOneWidget);
|
|
|
|
// 键盘应停靠在屏幕下半部(底部 16h + 面板高度内)。
|
|
final confirmRect = tester.getRect(find.text('确认'));
|
|
expect(confirmRect.bottom, lessThan(1080));
|
|
expect(confirmRect.bottom, greaterThan(1080 * 0.5));
|
|
// 默认停靠位为底部水平居中。
|
|
expect(confirmRect.center.dx, closeTo(960, 1));
|
|
|
|
// 点击数字 1 与 5 后字符进入目标控制器。
|
|
await tester.tap(find.text('1'));
|
|
await tester.pump();
|
|
await tester.tap(find.text('5'));
|
|
await tester.pump();
|
|
expect(controller.text, '15');
|
|
|
|
// 切换字母与符号面板均不产生布局溢出。
|
|
await tester.tap(find.text('ABC'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('#+='));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('123'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('在 AppDialog 弹框内弹出键盘仍然渲染', (tester) async {
|
|
final passwordController = TextEditingController();
|
|
tester.view.physicalSize = const Size(1920, 1080);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
wrapHost(
|
|
(context) => Scaffold(
|
|
body: Center(
|
|
child: FButton(
|
|
onPress: () => showAppDialog<void>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
canPop: false,
|
|
builder: (dialogContext) => AppDialog(
|
|
title: const Text('终端操作验证'),
|
|
width: 680,
|
|
maxWidth: 680,
|
|
content: FTextField(
|
|
controller: passwordController,
|
|
keyboardType: TextInputType.none,
|
|
onTap: () => AppSoftKeyboard.show(
|
|
dialogContext,
|
|
controller: passwordController,
|
|
mode: AppSoftKeyboardMode.digits,
|
|
),
|
|
),
|
|
actions: [
|
|
FButton(
|
|
style: FButtonStyle.outline,
|
|
label: const Text('取消'),
|
|
onPress: () => Navigator.of(dialogContext).pop(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
label: const Text('打开验证弹框'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('打开验证弹框'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// 弹框内的输入框应可见。
|
|
expect(find.text('终端操作验证'), findsOneWidget);
|
|
|
|
await tester.tap(find.byType(TextField).first);
|
|
await tester.pumpAndSettle();
|
|
|
|
// 键盘面板在弹框之上仍应渲染。
|
|
expect(find.text('确认'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('内嵌面板在弹框内渲染并输入', (tester) async {
|
|
final controller = TextEditingController();
|
|
tester.view.physicalSize = const Size(1920, 1080);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
wrapHost(
|
|
(context) => Scaffold(
|
|
body: Center(
|
|
child: AppSoftKeyboardPanel(
|
|
controller: controller,
|
|
mode: AppSoftKeyboardMode.digits,
|
|
allowPattern: RegExp('[0-9]'),
|
|
maxLength: 12,
|
|
).tight(width: 624),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// 面板切换 tab 与数字键应渲染;内嵌模式没有确认键。
|
|
expect(find.text('123'), findsOneWidget);
|
|
expect(find.text('确认'), findsNothing);
|
|
|
|
await tester.tap(find.text('2'));
|
|
await tester.pump();
|
|
await tester.tap(find.text('5'));
|
|
await tester.pump();
|
|
await tester.tap(find.text('0'));
|
|
await tester.pump();
|
|
await tester.tap(find.text('3'));
|
|
await tester.pump();
|
|
expect(controller.text, '2503');
|
|
|
|
// 清空键一键清空。
|
|
await tester.tap(find.text('清空'));
|
|
await tester.pump();
|
|
expect(controller.text, '');
|
|
|
|
// 内嵌宽度(624)下切换字母与符号面板同样不溢出。
|
|
await tester.tap(find.text('ABC'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('#+='));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('左侧停靠模式下键盘位于左半屏且垂直居中', (tester) async {
|
|
final controller = TextEditingController();
|
|
tester.view.physicalSize = const Size(1920, 1080);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
wrapHost(
|
|
(context) => Scaffold(
|
|
body: Center(
|
|
child: FButton(
|
|
onPress: () => AppSoftKeyboard.show(
|
|
context,
|
|
controller: controller,
|
|
mode: AppSoftKeyboardMode.digits,
|
|
dock: AppSoftKeyboardDock.centerLeft,
|
|
),
|
|
label: const Text('打开键盘'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('打开键盘'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final confirmRect = tester.getRect(find.text('确认'));
|
|
// 面板贴左缘停靠,整体落在左半屏。
|
|
expect(confirmRect.left, greaterThanOrEqualTo(0));
|
|
expect(confirmRect.right, lessThan(1920 * 0.5));
|
|
// 垂直居中:面板整体跨在屏幕纵向中线附近。
|
|
expect(
|
|
confirmRect.center.dy,
|
|
inExclusiveRange(1080 * 0.25, 1080 * 0.75),
|
|
);
|
|
|
|
// 输入与确认收起流程不受停靠位影响。
|
|
await tester.tap(find.text('7'));
|
|
await tester.pump();
|
|
expect(controller.text, '7');
|
|
await tester.tap(find.text('确认'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('确认'), findsNothing);
|
|
});
|
|
|
|
testWidgets('浮层键盘不抢走输入框焦点', (tester) async {
|
|
final controller = TextEditingController();
|
|
tester.view.physicalSize = const Size(1920, 1080);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
wrapHost(
|
|
(context) => Scaffold(
|
|
body: Center(
|
|
child: SizedBox(
|
|
width: 480,
|
|
child: FTextField(
|
|
controller: controller,
|
|
keyboardType: TextInputType.none,
|
|
onTap: () => AppSoftKeyboard.show(
|
|
context,
|
|
controller: controller,
|
|
mode: AppSoftKeyboardMode.digits,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byType(EditableText));
|
|
await tester.pumpAndSettle();
|
|
|
|
// 键盘弹出后输入框必须保持聚焦:主焦点不落入弹层,聚焦描边不消失,
|
|
// 实体键盘与扫码枪输入仍进入该输入框。
|
|
final editable = tester.widget<EditableText>(find.byType(EditableText));
|
|
expect(editable.focusNode.hasFocus, isTrue);
|
|
expect(find.text('确认'), findsOneWidget);
|
|
|
|
// 聚焦保持的同时虚拟按键照常写入。
|
|
await tester.tap(find.text('3'));
|
|
await tester.pump();
|
|
expect(controller.text, '3');
|
|
|
|
// 点击键盘外收起后输入框仍保持聚焦。
|
|
await tester.tapAt(const Offset(1800, 60));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('确认'), findsNothing);
|
|
expect(editable.focusNode.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('按键按压时整键缩放、抬起复原且完成输入', (tester) async {
|
|
final controller = TextEditingController();
|
|
tester.view.physicalSize = const Size(1920, 1080);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
wrapHost(
|
|
(context) => Scaffold(
|
|
body: Center(
|
|
child: FButton(
|
|
onPress: () => AppSoftKeyboard.show(
|
|
context,
|
|
controller: controller,
|
|
mode: AppSoftKeyboardMode.digits,
|
|
),
|
|
label: const Text('打开键盘'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('打开键盘'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final keyScale = find
|
|
.ancestor(of: find.text('5'), matching: find.byType(AnimatedScale))
|
|
.first;
|
|
expect(tester.widget<AnimatedScale>(keyScale).scale, 1.0);
|
|
// 抬起态为纯色底,无渐变。
|
|
final keyContainer = find
|
|
.ancestor(of: find.text('5'), matching: find.byType(AnimatedContainer))
|
|
.first;
|
|
var keyDecoration =
|
|
tester.widget<AnimatedContainer>(keyContainer).decoration
|
|
as BoxDecoration;
|
|
expect(keyDecoration.gradient, isNull);
|
|
|
|
// 指针按下未抬起:键帽立即进入按压缩放态并铺上主题色渐变。
|
|
final gesture = await tester.startGesture(
|
|
tester.getCenter(find.text('5')),
|
|
);
|
|
await tester.pump();
|
|
expect(tester.widget<AnimatedScale>(keyScale).scale, closeTo(0.92, 0.001));
|
|
keyDecoration = tester.widget<AnimatedContainer>(keyContainer).decoration
|
|
as BoxDecoration;
|
|
expect(keyDecoration.gradient, isA<LinearGradient>());
|
|
|
|
// 抬起后键帽复原、渐变退回纯色并完成一次输入。
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(tester.widget<AnimatedScale>(keyScale).scale, 1.0);
|
|
keyDecoration = tester.widget<AnimatedContainer>(keyContainer).decoration
|
|
as BoxDecoration;
|
|
expect(keyDecoration.gradient, isNull);
|
|
expect(controller.text, '5');
|
|
});
|
|
|
|
testWidgets('长按退格键持续删除,抬起即停', (tester) async {
|
|
final semanticsHandle = tester.ensureSemantics();
|
|
|
|
final controller = TextEditingController(text: '12345678');
|
|
tester.view.physicalSize = const Size(1920, 1080);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
wrapHost(
|
|
(context) => Scaffold(
|
|
body: Center(
|
|
child: FButton(
|
|
onPress: () => AppSoftKeyboard.show(
|
|
context,
|
|
controller: controller,
|
|
mode: AppSoftKeyboardMode.digits,
|
|
),
|
|
label: const Text('打开键盘'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('打开键盘'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final deleteFinder = find.bySemanticsLabel('退格删除,长按连续删除');
|
|
final gesture = await tester.startGesture(
|
|
tester.getCenter(deleteFinder),
|
|
);
|
|
|
|
// 长按阈值(500ms)到达时触发第一次删除,连发尚未开始。
|
|
await tester.pump(const Duration(milliseconds: 550));
|
|
expect(controller.text, '1234567');
|
|
|
|
// 连发周期 100ms:推进 340ms 触发 600/700/800 三个周期。
|
|
await tester.pump(const Duration(milliseconds: 340));
|
|
expect(controller.text, '1234');
|
|
|
|
// 抬起后连发立即停止,文本不再变化。
|
|
await gesture.up();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(controller.text, '1234');
|
|
await tester.pumpAndSettle();
|
|
|
|
// 单击退格仍然只删除一位。
|
|
await tester.tap(deleteFinder);
|
|
await tester.pump();
|
|
expect(controller.text, '123');
|
|
|
|
semanticsHandle.dispose();
|
|
});
|
|
}
|