// feature_auth 的高价值断言: // 1. 登录失败不能变成未捕获异常("密码错了"不是崩溃),要留在 state 里展示。 // 2. 提交中按钮必须置灰——门店网络慢,用户会反复点。 import 'dart:async'; import 'package:core_auth/core_auth.dart'; import 'package:core_foundation/core_foundation.dart'; import 'package:feature_auth/feature_auth.dart'; // LoginPage 不对外导出(别的包没有直接构造它的正当理由),测试走 src。 import 'package:feature_auth/src/presentation/login_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; class _FailingRepo implements AuthRepository { /// 非 null 时 login 挂在这个 completer 上,用来观察"请求在途"这一帧。 Completer? gate; int calls = 0; String? lastUsername; @override Future login({required String username, required String password}) { calls++; lastUsername = username; return gate?.future ?? Future.error(const BusinessException(40101, '账号或密码错误')); } @override Future fetchCurrentUser() => throw UnimplementedError(); @override Future> fetchAccessibleStores() => throw UnimplementedError(); @override Future switchStore(int storeId) => throw UnimplementedError(); @override Future revokeSession() => throw UnimplementedError(); } void main() { testWidgets('登录失败展示业务文案,不抛出未捕获异常', (WidgetTester tester) async { final _FailingRepo repo = _FailingRepo(); await tester.pumpWidget( ProviderScope( overrides: [authRepositoryProvider.overrideWithValue(repo)], child: const MaterialApp(home: LoginPage()), ), ); // 前后空格是扫码枪/手动输入的常见污染,必须在提交前 trim。 await tester.enterText(find.byKey(const Key('login_username')), ' clerk01 '); await tester.enterText(find.byKey(const Key('login_password')), 'pwd'); await tester.tap(find.byKey(const Key('login_submit'))); await tester.pump(); expect(repo.lastUsername, 'clerk01'); await tester.pumpAndSettle(); expect(find.text('账号或密码错误'), findsOneWidget); expect(tester.takeException(), isNull); }); testWidgets('提交中按钮置灰,重复点击不会重复发请求', (WidgetTester tester) async { final _FailingRepo repo = _FailingRepo()..gate = Completer(); await tester.pumpWidget( ProviderScope( overrides: [authRepositoryProvider.overrideWithValue(repo)], child: const MaterialApp(home: LoginPage()), ), ); await tester.tap(find.byKey(const Key('login_submit'))); await tester.pump(); // 请求还挂在 gate 上,这一帧就是"提交中" final FilledButton button = tester.widget(find.byKey(const Key('login_submit'))); expect(button.onPressed, isNull, reason: '门店网络慢,用户会反复点'); await tester.tap(find.byKey(const Key('login_submit')), warnIfMissed: false); await tester.pump(); expect(repo.calls, 1); repo.gate!.completeError(const BusinessException(40101, '账号或密码错误')); await tester.pumpAndSettle(); }); }