Files

155 lines
5.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:observer/auth/auth_screen.dart';
import 'package:observer/auth/auth_view_model.dart';
import 'package:observer/auth/session_store.dart';
import 'package:observer/home/home_screen.dart';
import 'package:observer/home/home_view_model.dart';
import 'package:observer/payment/license_service.dart';
import 'package:observer/payment/models.dart';
import 'package:observer/payment/order_api.dart';
import 'package:provider/provider.dart';
/// 测试环境无 secure storage 平台实现:mock 通道让读写立即返回空。
void _mockSecureStorage() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
(call) async => null,
);
}
/// 内存版 SessionStore:不触平台通道(flutter_secure_storage 在测试环境不可用)
class FakeSession extends SessionStore {
String? token;
String? phone;
bool cleared = false;
@override
Future<String?> readToken() async => token;
@override
Future<String?> readPhone() async => phone;
@override
Future<void> save(String phone, String token) async {
this.phone = phone;
this.token = token;
}
@override
Future<void> clear() async {
token = null;
phone = null;
cleared = true;
}
}
/// 内存版 OrderApi:登录/注册/授权全部本地返回,不触网络
class FakeOrderApi extends OrderApi {
final LicenseStatus licenseResult;
FakeOrderApi(SessionStore s, {this.licenseResult = const LicenseStatus(active: false)})
: super(sessionStore: s);
@override
Future<void> register({required String phone, required String password}) async {}
@override
Future<String> login({required String phone, required String password}) async =>
'fake-token';
@override
Future<LicenseStatus> fetchLicense() async => licenseResult;
}
Widget _app(FakeSession session, FakeOrderApi api,
{String initialRoute = '/login'}) {
final authVm = AuthViewModel(orderApi: api, sessionStore: session);
final homeVm = HomeViewModel(
licenseService: LicenseService(orderApi: api, sessionStore: session));
return MultiProvider(
providers: [
Provider.value(value: session),
ChangeNotifierProvider.value(value: authVm),
ChangeNotifierProvider.value(value: homeVm),
],
child: MaterialApp(
initialRoute: initialRoute,
routes: {
'/login': (_) => const AuthScreen(),
'/home': (_) => const HomeScreen(),
'/camera': (_) => const Scaffold(body: Text('camera-stub')),
},
),
);
}
Future<void> _enterCredentials(WidgetTester tester, {required bool register}) async {
if (register) {
await tester.tap(find.text('没有账号?去注册'));
await tester.pumpAndSettle();
}
await tester.enterText(find.widgetWithText(TextField, '手机号'), '13912345678');
await tester.enterText(find.widgetWithText(TextField, '密码'), 'pass123456');
}
void main() {
setUp(_mockSecureStorage);
testWidgets('登录流程:输错密码提示错误,不跳转', (tester) async {
final session = FakeSession();
final api = FakeOrderApi(session);
await tester.pumpWidget(_app(session, api));
await tester.tap(find.text('登录'));
await tester.pumpAndSettle();
// 本地校验:空手机号提示
expect(find.text('请输入手机号和至少 6 位密码'), findsOneWidget);
});
testWidgets('注册并登录成功 → 跳转主界面展示未充值', (tester) async {
final session = FakeSession();
final api = FakeOrderApi(session);
await tester.pumpWidget(_app(session, api));
await _enterCredentials(tester, register: true);
await tester.tap(find.text('注册并登录'));
await tester.pumpAndSettle();
expect(find.byType(HomeScreen), findsOneWidget);
expect(session.token, 'fake-token');
expect(session.phone, '13912345678');
expect(find.textContaining('未充值'), findsOneWidget);
expect(find.text('搜索'), findsOneWidget);
expect(find.text('充值'), findsOneWidget);
});
testWidgets('搜索按钮强制服务端校验:未授权弹出拦截弹窗', (tester) async {
final session = FakeSession()..token = 'fake-token';
final api = FakeOrderApi(session); // active: false
await tester.pumpWidget(_app(session, api, initialRoute: '/home'));
await tester.tap(find.text('搜索'));
await tester.pumpAndSettle();
expect(find.text('授权不可用'), findsOneWidget);
expect(find.text('去充值'), findsOneWidget);
});
testWidgets('搜索按钮校验通过 → 进入相机页', (tester) async {
final session = FakeSession()..token = 'fake-token';
final api = FakeOrderApi(
session,
licenseResult: LicenseStatus(
active: true,
expiresAt: DateTime.now().add(const Duration(days: 1))),
);
await tester.pumpWidget(_app(session, api, initialRoute: '/home'));
await tester.tap(find.text('搜索'));
await tester.pumpAndSettle();
expect(find.text('camera-stub'), findsOneWidget);
});
}