|
|
@@ -107,6 +107,17 @@ void main() {
|
|
|
await tester.pumpAndSettle();
|
|
|
}
|
|
|
|
|
|
+ /// 等防抖走完,取出真正送到通道的那条表达式。
|
|
|
+ ///
|
|
|
+ /// 显示区那行"原文"是按需出现的(没信息量就不显示),所以断言文档内容
|
|
|
+ /// 不能靠它 —— 看通道里实际发出去的东西才可靠。
|
|
|
+ Future<String> lastExpr(WidgetTester tester) async {
|
|
|
+ await tester.pump(const Duration(milliseconds: 700));
|
|
|
+ await tester.pumpAndSettle();
|
|
|
+ return calls.lastWhere((c) => c.method == 'evaluate').arguments['expr']
|
|
|
+ as String;
|
|
|
+ }
|
|
|
+
|
|
|
group('外壳', () {
|
|
|
testWidgets('三个界面:计算器 / 工具 / 控制台', (tester) async {
|
|
|
await pumpApp(tester);
|
|
|
@@ -132,7 +143,7 @@ void main() {
|
|
|
await press(tester, '∫');
|
|
|
await press(tester, 'x');
|
|
|
|
|
|
- expect(find.text('Integrate[x, x]'), findsOneWidget);
|
|
|
+ expect(await lastExpr(tester), 'Integrate[x, x]');
|
|
|
});
|
|
|
|
|
|
testWidgets('^ 把左边的节点收成底,光标落到指数', (tester) async {
|
|
|
@@ -141,7 +152,7 @@ void main() {
|
|
|
await press(tester, '^');
|
|
|
await press(tester, '2');
|
|
|
|
|
|
- expect(find.text('x^2'), findsOneWidget);
|
|
|
+ expect(await lastExpr(tester), 'x^2');
|
|
|
});
|
|
|
|
|
|
testWidgets('数字键并入同一个字面量', (tester) async {
|
|
|
@@ -196,7 +207,7 @@ void main() {
|
|
|
await press(tester, 'x');
|
|
|
await press(tester, '+');
|
|
|
await press(tester, '1');
|
|
|
- expect(find.text('x+1'), findsOneWidget);
|
|
|
+ expect(await lastExpr(tester), 'x+1');
|
|
|
|
|
|
// 点 x 的左半边 → 光标应当落到 x 前面
|
|
|
final xRect = tester.getRect(
|
|
|
@@ -207,7 +218,7 @@ void main() {
|
|
|
|
|
|
// 再打一个 2:插到最前面才是 2x+1;若光标落在 x 之后就成 x2+1
|
|
|
await press(tester, '2');
|
|
|
- expect(find.text('2x+1'), findsOneWidget);
|
|
|
+ expect(await lastExpr(tester), '2x+1');
|
|
|
});
|
|
|
|
|
|
testWidgets('点节点右半边把光标放到它后面', (tester) async {
|
|
|
@@ -222,7 +233,7 @@ void main() {
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await press(tester, '2');
|
|
|
- expect(find.text('x2+'), findsOneWidget);
|
|
|
+ expect(await lastExpr(tester), 'x2+');
|
|
|
});
|
|
|
|
|
|
testWidgets('默认精确:求值原样送出去', (tester) async {
|
|
|
@@ -278,8 +289,7 @@ void main() {
|
|
|
|
|
|
group('复制粘贴', () {
|
|
|
/// 把系统通道的剪贴板换成内存版,并记录写入。
|
|
|
- ({List<String> copied, void Function(String?) feed}) mockClipboard(
|
|
|
- WidgetTester tester) {
|
|
|
+ List<String> mockClipboard(WidgetTester tester) {
|
|
|
final copied = <String>[];
|
|
|
String? available;
|
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
|
@@ -291,7 +301,9 @@ void main() {
|
|
|
available = t; // 有写有读,跟真剪贴板一样
|
|
|
return null;
|
|
|
case 'Clipboard.getData':
|
|
|
- return available == null ? null : <String, dynamic>{'text': available};
|
|
|
+ return available == null
|
|
|
+ ? null
|
|
|
+ : <String, dynamic>{'text': available};
|
|
|
default:
|
|
|
return null;
|
|
|
}
|
|
|
@@ -299,123 +311,95 @@ void main() {
|
|
|
addTearDown(() => TestDefaultBinaryMessengerBinding
|
|
|
.instance.defaultBinaryMessenger
|
|
|
.setMockMethodCallHandler(SystemChannels.platform, null));
|
|
|
- return (copied: copied, feed: (v) => available = v);
|
|
|
+ return copied;
|
|
|
}
|
|
|
|
|
|
- Future<void> pickMenu(WidgetTester tester, String item) async {
|
|
|
- await tester.tap(find.byIcon(Icons.more_vert));
|
|
|
+ Finder inEditor(String text) => find.descendant(
|
|
|
+ of: find.byType(ExprEditor), matching: find.text(text));
|
|
|
+
|
|
|
+ /// 长按 [from] 再点 [to],选中两者之间(含)的一段。
|
|
|
+ Future<void> selectRange(
|
|
|
+ WidgetTester tester, String from, String to) async {
|
|
|
+ await tester.longPress(inEditor(from));
|
|
|
await tester.pumpAndSettle();
|
|
|
- await tester.tap(find.text(item));
|
|
|
+ await tester.tap(inEditor(to));
|
|
|
await tester.pumpAndSettle();
|
|
|
}
|
|
|
|
|
|
- Finder inEditor(String text) => find.descendant(
|
|
|
- of: find.byType(ExprEditor), matching: find.text(text));
|
|
|
+ /// 点"光标已经所在的地方",弹出粘贴入口。
|
|
|
+ ///
|
|
|
+ /// 直接点光标那根竖线最稳 —— 光标在哪,就点到哪。按节点坐标算的话,
|
|
|
+ /// 光标在某个参数槽里时点节点边界其实落不到同一个位置。
|
|
|
+ Future<void> tapCaret(WidgetTester tester) async {
|
|
|
+ await tester
|
|
|
+ .tapAt(tester.getCenter(find.byKey(const ValueKey('expr-caret'))));
|
|
|
+ await tester.pumpAndSettle();
|
|
|
+ }
|
|
|
|
|
|
- testWidgets('长按选中一段 → 操作栏自动出现 → 剪切', (tester) async {
|
|
|
- final clip = mockClipboard(tester);
|
|
|
+ testWidgets('选中一段 → 操作栏自动出现 → 复制', (tester) async {
|
|
|
+ final copied = mockClipboard(tester);
|
|
|
await pumpApp(tester);
|
|
|
for (final k in ['2', 'x', '+', '1']) {
|
|
|
await press(tester, k);
|
|
|
}
|
|
|
|
|
|
- await tester.longPress(inEditor('2'));
|
|
|
- await tester.pumpAndSettle();
|
|
|
- // 选中后操作栏自动出现
|
|
|
- expect(find.textContaining('已选中'), findsOneWidget);
|
|
|
-
|
|
|
- // 再点另一个节点 → 扩展选区
|
|
|
- await tester.tap(inEditor('1'));
|
|
|
- await tester.pumpAndSettle();
|
|
|
+ await selectRange(tester, '2', '1');
|
|
|
expect(find.textContaining('已选中 2x+1'), findsOneWidget);
|
|
|
|
|
|
- await tester.tap(find.text('剪切'));
|
|
|
+ await tester.tap(find.text('复制'));
|
|
|
await tester.pumpAndSettle();
|
|
|
- expect(clip.copied, ['2x+1']);
|
|
|
- expect(find.text('Null'), findsOneWidget); // 全剪掉了
|
|
|
+ expect(copied, ['2x+1']);
|
|
|
});
|
|
|
|
|
|
testWidgets('剪切后粘到 Log 的括号里', (tester) async {
|
|
|
- final clip = mockClipboard(tester);
|
|
|
+ mockClipboard(tester);
|
|
|
await pumpApp(tester);
|
|
|
for (final k in ['2', 'x', '+', '1']) {
|
|
|
await press(tester, k);
|
|
|
}
|
|
|
- await tester.longPress(inEditor('2'));
|
|
|
- await tester.pumpAndSettle();
|
|
|
- await tester.tap(inEditor('1'));
|
|
|
- await tester.pumpAndSettle();
|
|
|
+ await selectRange(tester, '2', '1');
|
|
|
await tester.tap(find.text('剪切'));
|
|
|
await tester.pumpAndSettle();
|
|
|
+ expect(find.text('Null'), findsOneWidget); // 全剪掉了
|
|
|
|
|
|
- // 建一个空的 Log,光标在参数槽里
|
|
|
- await press(tester, 'ln');
|
|
|
+ await press(tester, 'ln'); // Log[Null],光标在参数槽里
|
|
|
expect(find.text('Log[Null]'), findsOneWidget);
|
|
|
|
|
|
- await pickMenu(tester, '从剪贴板粘贴');
|
|
|
- expect(find.text('Log[2x+1]'), findsWidgets);
|
|
|
- });
|
|
|
-
|
|
|
- testWidgets('点光标所在位置会弹出粘贴入口', (tester) async {
|
|
|
- final clip = mockClipboard(tester);
|
|
|
- clip.feed('7');
|
|
|
- await pumpApp(tester);
|
|
|
- await press(tester, 'x'); // 光标停在 x 之后
|
|
|
-
|
|
|
- // 点 x 的右半边 = 光标已经在的地方
|
|
|
- final r = tester.getRect(inEditor('x'));
|
|
|
- await tester.tapAt(Offset(r.right - 1, r.center.dy));
|
|
|
- await tester.pumpAndSettle();
|
|
|
+ await tapCaret(tester);
|
|
|
expect(find.text('粘贴到光标处'), findsOneWidget);
|
|
|
|
|
|
await tester.tap(find.text('粘贴'));
|
|
|
await tester.pumpAndSettle();
|
|
|
- expect(find.textContaining('已粘贴到光标处'), findsOneWidget);
|
|
|
+ expect(await lastExpr(tester), 'Log[2x+1]');
|
|
|
});
|
|
|
|
|
|
- testWidgets('复制表达式进剪贴板', (tester) async {
|
|
|
- final clip = mockClipboard(tester);
|
|
|
+ testWidgets('粘贴解析不了的东西会明确报错,不会乱猜', (tester) async {
|
|
|
+ mockClipboard(tester);
|
|
|
await pumpApp(tester);
|
|
|
await press(tester, 'x');
|
|
|
- await press(tester, '^');
|
|
|
- await press(tester, '2');
|
|
|
-
|
|
|
- await pickMenu(tester, '复制表达式');
|
|
|
- expect(clip.copied, ['x^2']);
|
|
|
- });
|
|
|
|
|
|
- testWidgets('粘贴一段 Symja 表达式,转成结构化输入', (tester) async {
|
|
|
- final clip = mockClipboard(tester);
|
|
|
- clip.feed('Integrate[x^2, x]');
|
|
|
- await pumpApp(tester);
|
|
|
-
|
|
|
- await pickMenu(tester, '从剪贴板粘贴');
|
|
|
- // 编辑器显示区和序列化行都会出现它
|
|
|
- expect(find.text('Integrate[x^2, x]'), findsWidgets);
|
|
|
- });
|
|
|
-
|
|
|
- testWidgets('粘贴解析不了的东西会明确报错,不会乱猜', (tester) async {
|
|
|
- final clip = mockClipboard(tester);
|
|
|
- clip.feed('x@y');
|
|
|
- await pumpApp(tester);
|
|
|
+ // 往剪贴板里塞一段解析不了的,再粘
|
|
|
+ await Clipboard.setData(const ClipboardData(text: 'x@y'));
|
|
|
+ await tapCaret(tester);
|
|
|
+ await tester.tap(find.text('粘贴'));
|
|
|
+ await tester.pumpAndSettle();
|
|
|
|
|
|
- await pickMenu(tester, '从剪贴板粘贴');
|
|
|
expect(find.textContaining('没法转成按钮式输入'), findsOneWidget);
|
|
|
});
|
|
|
|
|
|
- testWidgets('复制粘贴走一圈,结果一致', (tester) async {
|
|
|
- final clip = mockClipboard(tester);
|
|
|
+ testWidgets('点结果直接复制结果', (tester) async {
|
|
|
+ final copied = mockClipboard(tester);
|
|
|
await pumpApp(tester);
|
|
|
- for (final k in ['2', 'x', '+', '1']) {
|
|
|
- await press(tester, k);
|
|
|
- }
|
|
|
- await pickMenu(tester, '复制表达式');
|
|
|
- expect(clip.copied.single, '2x+1');
|
|
|
+ await press(tester, '2');
|
|
|
+ await press(tester, '+');
|
|
|
+ await press(tester, '3');
|
|
|
+ await press(tester, '=');
|
|
|
|
|
|
- clip.feed(clip.copied.single);
|
|
|
- await pickMenu(tester, '清空');
|
|
|
- await pickMenu(tester, '从剪贴板粘贴');
|
|
|
- expect(find.text('2x+1'), findsWidgets);
|
|
|
+ // 结果区渲染的是 LaTeX,纯文本那份不在,所以点整个结果条
|
|
|
+ await tester.tap(find.byKey(const ValueKey('calc-result')));
|
|
|
+ await tester.pumpAndSettle();
|
|
|
+ expect(copied, contains('x^3/3'));
|
|
|
+ expect(find.textContaining('已复制结果'), findsOneWidget);
|
|
|
});
|
|
|
});
|
|
|
|