flutter架 构项目基础
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user