129 lines
5.3 KiB
Dart
129 lines
5.3 KiB
Dart
// Widget 测试:登录页表单交互
|
||
//
|
||
// 测什么(都是不触网络的本地交互):
|
||
// ① 空账号点「立即登录」→ SnackBar 提示"请填写账号";
|
||
// ② 只有账号没密码 → 提示"请填写密码";
|
||
// ③ 账号密码都有但没勾协议 → 提示"请先阅读并同意《用户协议》和《隐私政策》";
|
||
// ④ 密码"眼睛"点击 → obscureText 在打码/明文间切换;
|
||
// ⑤ 勾选协议 → 未勾选图标消失(协议状态真的变了)。
|
||
//
|
||
// 为什么能跑:
|
||
// 登录页校验顺序是"先本地校验 → 再调接口"(login_page.dart _handleLogin),
|
||
// ①②③ 在调接口之前就 return,完全不碰网络/Dio。
|
||
// initState 异步读"记住的账号/密码"要 mock 两个存储插件(同 widget_test.dart)。
|
||
// LoginPage 用了 .w/.h/.sp → 必须包 ScreenUtilInit;用了 ref → 包 ProviderScope。
|
||
//
|
||
// 运行:flutter test test/widgets/login_form_test.dart
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
|
||
import 'package:new_learn/features/auth/login_page.dart';
|
||
|
||
/// 按登录页真实运行环境 pump(ScreenUtilInit + ProviderScope + MaterialApp)
|
||
Future<void> pumpLoginPage(WidgetTester tester) async {
|
||
// ⚠ 测试默认窗口是 800x600,ScreenUtil 会把 375 设计稿放大 ~2.1 倍,
|
||
// 登录页元素全部溢出屏幕外:find.text 找得到,但 tap 命中测试点不到。
|
||
// 先把手持窗口设成手机尺寸(逻辑 375x812,dpr=1),缩放比例回到 1。
|
||
tester.view.physicalSize = const Size(375, 812);
|
||
tester.view.devicePixelRatio = 1.0;
|
||
addTearDown(tester.view.reset);
|
||
|
||
await tester.pumpWidget(
|
||
ProviderScope(
|
||
child: ScreenUtilInit(
|
||
designSize: const Size(375, 812),
|
||
builder: (context, child) => const MaterialApp(home: LoginPage()),
|
||
),
|
||
),
|
||
);
|
||
await tester.pumpAndSettle();
|
||
}
|
||
|
||
/// 推进 3 秒走完 SnackBar 的 2s 计时器 + 退场动画(防测试结束时报 pending timer)
|
||
Future<void> flushSnackBar(WidgetTester tester) async {
|
||
await tester.pump(const Duration(seconds: 3));
|
||
await tester.pumpAndSettle();
|
||
}
|
||
|
||
void main() {
|
||
setUp(() {
|
||
SharedPreferences.setMockInitialValues({});
|
||
FlutterSecureStorage.setMockInitialValues({});
|
||
});
|
||
|
||
testWidgets('① 空账号点登录 → 提示"请填写账号"', (WidgetTester tester) async {
|
||
await pumpLoginPage(tester);
|
||
|
||
await tester.tap(find.text('立即登录'));
|
||
await tester.pump(); // SnackBar 入场动画第一帧
|
||
|
||
expect(find.text('请填写账号'), findsOneWidget);
|
||
await flushSnackBar(tester);
|
||
});
|
||
|
||
testWidgets('② 只有账号没密码 → 提示"请填写密码"', (WidgetTester tester) async {
|
||
await pumpLoginPage(tester);
|
||
|
||
await tester.enterText(find.byType(TextField).first, '13800138000');
|
||
await tester.tap(find.text('立即登录'));
|
||
await tester.pump();
|
||
|
||
expect(find.text('请填写密码'), findsOneWidget);
|
||
await flushSnackBar(tester);
|
||
});
|
||
|
||
testWidgets('③ 账号密码齐全但没勾协议 → 提示先阅读并同意协议', (WidgetTester tester) async {
|
||
await pumpLoginPage(tester);
|
||
|
||
await tester.enterText(find.byType(TextField).first, '13800138000');
|
||
await tester.enterText(find.byType(TextField).at(1), '123456');
|
||
await tester.tap(find.text('立即登录'));
|
||
await tester.pump();
|
||
|
||
expect(find.text('请先阅读并同意《用户协议》和《隐私政策》'), findsOneWidget);
|
||
await flushSnackBar(tester);
|
||
});
|
||
|
||
testWidgets('④ 密码"眼睛"点击 → obscureText 在打码/明文间切换', (WidgetTester tester) async {
|
||
await pumpLoginPage(tester);
|
||
|
||
final passwordField = find.byType(TextField).at(1);
|
||
// 初始:密码打码
|
||
expect(tester.widget<TextField>(passwordField).obscureText, isTrue);
|
||
|
||
// 点击"眼睛"图标 → 明文
|
||
await tester.tap(find.byIcon(Icons.visibility_off_outlined));
|
||
await tester.pump();
|
||
expect(tester.widget<TextField>(passwordField).obscureText, isFalse);
|
||
// 图标本身也切换了
|
||
expect(find.byIcon(Icons.visibility_outlined), findsOneWidget);
|
||
|
||
// 再点一次 → 回到打码
|
||
await tester.tap(find.byIcon(Icons.visibility_outlined));
|
||
await tester.pump();
|
||
expect(tester.widget<TextField>(passwordField).obscureText, isTrue);
|
||
});
|
||
|
||
testWidgets('⑤ 勾选协议 → 未勾选图标消失', (WidgetTester tester) async {
|
||
await pumpLoginPage(tester);
|
||
|
||
// 初始:记住密码默认勾选(check_circle),协议未勾选(radio_button_unchecked)
|
||
final agreementCheckbox = find.byIcon(Icons.radio_button_unchecked);
|
||
expect(agreementCheckbox, findsOneWidget);
|
||
|
||
// 协议区在页面底部,SingleChildScrollView 内容超出 812 视口,
|
||
// 直接 tap 会点到屏幕外 → 先 ensureVisible 滚到可见再点
|
||
await tester.ensureVisible(agreementCheckbox);
|
||
await tester.tap(agreementCheckbox);
|
||
await tester.pump();
|
||
|
||
// 协议已勾选 → 页面上不再有未勾选图标
|
||
expect(find.byIcon(Icons.radio_button_unchecked), findsNothing);
|
||
});
|
||
}
|