155 lines
5.1 KiB
Dart
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);
|
|
});
|
|
}
|