Files
App.K12.Student.Miniapp_Flu…/test/widgets/login_form_test.dart
T
2026-09-04 15:22:20 +08:00

129 lines
5.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);
});
}