Files
conti-retail-app/packages/feature_auth/test/login_page_test.dart
T

90 lines
3.3 KiB
Dart
Raw Normal View History

2026-08-17 15:29:55 +08:00
// 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<LoginResult>? gate;
int calls = 0;
String? lastUsername;
@override
Future<LoginResult> login({required String username, required String password}) {
calls++;
lastUsername = username;
return gate?.future ?? Future<LoginResult>.error(const BusinessException(40101, '账号或密码错误'));
}
@override
Future<UserContext> fetchCurrentUser() => throw UnimplementedError();
@override
Future<List<StoreContext>> fetchAccessibleStores() => throw UnimplementedError();
@override
Future<StoreContext> switchStore(int storeId) => throw UnimplementedError();
@override
Future<void> 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<LoginResult>();
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();
});
}