// 功能描述:验证用户端登录页字段、必填校验、提交行为和接口错误中文化。 // 版本:1.2.0 import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:user_app/app/dependencies.dart'; import 'package:user_app/data/services/api_client.dart'; import 'package:user_app/data/services/secure_session_store.dart'; import 'package:user_app/ui/features/auth/login_page.dart'; /// 记录登录调用,验证本地校验是否正确阻止请求。 class _RecordingUserSession extends UserSession { _RecordingUserSession() : super(SecureSessionStore()); int loginCallCount = 0; String? lastPhone; String? lastPassword; String? lastCode; int codeCalls = 0; @override Future sendCode(String phone, String purpose) async { codeCalls++; return 'request-1'; } @override Future login({ required String phone, required String password, String? verificationCode, String? requestIdentity, }) async { loginCallCount += 1; lastPhone = phone; lastPassword = password; lastCode = verificationCode; } } /// 模拟服务端确认短信未实际发送,验证页面不会显示虚假倒计时。 class _UnavailableCodeSession extends _RecordingUserSession { @override Future sendCode(String phone, String purpose) async { throw const ApiException(2410, '短信验证码暂未开放'); } } /// 验证用户端登录界面的关键交互。 void main() { testWidgets('显隐保留密码和焦点,重复选择当前登录方式不清空输入', (tester) async { await tester.pumpWidget(MaterialApp(home: LoginPage(session: _RecordingUserSession()))); await tester.tap(find.text('密码登录')); await tester.pumpAndSettle(); await tester.enterText(find.byType(TextField).at(1), 'Input-test-123'); await tester.tap(find.byTooltip('显示密码')); await tester.pumpAndSettle(); var field = tester.widget(find.byType(TextField).at(1)); expect(field.controller!.text, 'Input-test-123'); expect(field.obscureText, isFalse); expect(field.focusNode!.hasFocus, isTrue); await tester.tap(find.text('密码登录')); await tester.pumpAndSettle(); expect( tester.widget(find.byType(TextField).at(1)).controller!.text, 'Input-test-123', ); await tester.tap(find.byTooltip('隐藏密码')); await tester.pumpAndSettle(); field = tester.widget(find.byType(TextField).at(1)); expect(field.obscureText, isTrue); expect(field.controller!.text, 'Input-test-123'); expect(tester.takeException(), isNull); }); testWidgets('有焦点时切换验证码建立新连接并清除上一种凭据', (tester) async { await tester.pumpWidget(MaterialApp(home: LoginPage(session: _RecordingUserSession()))); await tester.tap(find.text('密码登录')); await tester.pumpAndSettle(); await tester.enterText(find.byType(TextField).at(1), 'Input-test-123'); await tester.tap(find.text('验证码登录')); await tester.pumpAndSettle(); final field = tester.widget(find.byType(TextField).at(1)); expect(field.controller!.text, isEmpty); expect(field.keyboardType, TextInputType.number); expect(field.obscureText, isFalse); expect(field.focusNode!.hasFocus, isTrue); await tester.enterText(find.byType(TextField).at(1), '314159'); expect(field.controller!.text, '314159'); expect(tester.takeException(), isNull); }); testWidgets('login page exposes phone and password fields', (tester) async { final session = UserSession(SecureSessionStore()); await tester.pumpWidget(MaterialApp(home: LoginPage(session: session))); expect(find.bySemanticsLabel('瓶安芯'), findsOneWidget); expect(find.byType(TextField), findsNWidgets(2)); expect(find.text('登录'), findsOneWidget); }); testWidgets('手机号错误且密码为空时同时提示并阻止登录调用', (tester) async { final session = _RecordingUserSession(); await tester.pumpWidget(MaterialApp(home: LoginPage(session: session))); await tester.tap(find.text('密码登录')); await tester.pump(); await tester.enterText(find.byType(TextField).first, '+8613800138000'); await tester.ensureVisible(find.text('登录')); await tester.tap(find.text('登录')); await tester.pump(); expect(find.text('请输入正确的11位手机号'), findsOneWidget); expect(tester.widget(find.byType(TextField).at(1)).decoration?.errorText, '请输入密码'); expect(find.text('Invalid Argument'), findsNothing); expect(session.loginCallCount, 0); expect(tester.widget(find.byType(TextField).first).focusNode?.hasFocus, isTrue); }); testWidgets('合法手机号配空密码时提示密码必填并阻止登录调用', (tester) async { final session = _RecordingUserSession(); await tester.pumpWidget(MaterialApp(home: LoginPage(session: session))); await tester.tap(find.text('密码登录')); await tester.pump(); await tester.enterText(find.byType(TextField).first, '13800138000'); await tester.ensureVisible(find.text('登录')); await tester.tap(find.text('登录')); await tester.pump(); expect(find.text('请输入正确的11位手机号'), findsNothing); expect(tester.widget(find.byType(TextField).at(1)).decoration?.errorText, '请输入密码'); expect(session.loginCallCount, 0); expect(tester.widget(find.byType(TextField).at(1)).focusNode?.hasFocus, isTrue); }); testWidgets('修改字段清除对应错误并由回车原样提交密码', (tester) async { final session = _RecordingUserSession(); await tester.pumpWidget(MaterialApp(home: LoginPage(session: session))); await tester.tap(find.text('密码登录')); await tester.pump(); await tester.ensureVisible(find.text('登录')); await tester.tap(find.text('登录')); await tester.pump(); await tester.enterText(find.byType(TextField).first, '13800138000'); await tester.pump(); expect(find.text('请输入正确的11位手机号'), findsNothing); expect(tester.widget(find.byType(TextField).at(1)).decoration?.errorText, '请输入密码'); await tester.enterText(find.byType(TextField).at(1), ' 密码123 '); await tester.pump(); expect(tester.widget(find.byType(TextField).at(1)).decoration?.errorText, isNull); await tester.testTextInput.receiveAction(TextInputAction.done); await tester.pump(); expect(session.loginCallCount, 1); expect(session.lastPhone, '13800138000'); expect(session.lastPassword, ' 密码123 '); }); test('接口错误优先按错误码中文化并屏蔽未知英文', () { expect(localizeApiErrorMessage(1704, 'Invalid Argument'), '请求参数错误'); expect(localizeApiErrorMessage(1108, 'Password Incorrect'), '密码错误'); expect(localizeApiErrorMessage(2410, 'service unavailable'), '短信验证码暂未开放'); expect(localizeApiErrorMessage(9999, '业务处理失败'), '业务处理失败'); expect(localizeApiErrorMessage(9999, 'Unexpected Error'), '操作失败,请稍后重试'); }); testWidgets('验证码发送后修改手机号不得复用旧请求', (tester) async { final session = _RecordingUserSession(); await tester.pumpWidget(MaterialApp(home: LoginPage(session: session))); await tester.enterText(find.byType(TextField).first, '13800138000'); await tester.tap(find.text('获取验证码')); await tester.pump(); await tester.enterText(find.byType(TextField).first, '13800138001'); await tester.enterText(find.byType(TextField).at(1), '123456'); await tester.ensureVisible(find.text('登录')); await tester.tap(find.text('登录')); await tester.pump(); expect(find.text('请先获取当前手机号的验证码'), findsOneWidget); expect(session.loginCallCount, 0); await tester.pumpWidget(const SizedBox()); }); testWidgets('短信未实际发送时明确提示暂未开放且不启动倒计时', (tester) async { await tester.pumpWidget(MaterialApp(home: LoginPage(session: _UnavailableCodeSession()))); await tester.enterText(find.byType(TextField).first, '13800138000'); await tester.tap(find.text('获取验证码')); await tester.pump(); expect(find.text('短信验证码暂未开放'), findsOneWidget); expect(find.text('60s'), findsNothing); }); }