Files
pangolin/client/test/widget/auth_invite_field_test.dart

104 lines
3.7 KiB
Dart

// auth_invite_field_test.dart — Task 13: 注册页「邀请码(选填)」输入框
//
// 驱动路径:切到注册 tab(step0)→ 填邮箱 → 点发送验证码(mock 网络 204)→
// 填 6 位验证码 → 点下一步(进 step1 设密码步)→ 断言 step1 渲染出
// key 为 invite-code-field 的 TextField,label 为 t.inviteCodeFieldLabel。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:pangolin_vpn/l10n/strings_zh.dart';
import 'package:pangolin_vpn/pangolin_theme.dart';
import 'package:pangolin_vpn/services/auth_api.dart';
import 'package:pangolin_vpn/services/token_store.dart';
import 'package:pangolin_vpn/state/auth_provider.dart';
import 'package:pangolin_vpn/widgets/auth_screen.dart';
import '../helpers/harness.dart';
class _NullTokenStore implements TokenStore {
const _NullTokenStore();
@override
Future<void> saveTokens({required String access, required String refresh}) async {}
@override
Future<String?> loadAccessToken() async => null;
@override
Future<String?> loadRefreshToken() async => null;
@override
Future<void> clear() async {}
@override
Future<void> markOnboarded() async {}
@override
Future<bool> isOnboarded() async => true;
@override
Future<void> saveLastEmail(String email) async {}
@override
Future<String?> loadLastEmail() async => null;
}
void main() {
setUpAll(disableGoogleFontsFetching);
const t = StringsZh();
Future<void> _driveToRegisterStep1(WidgetTester tester) async {
final mockClient = MockClient((req) async {
if (req.method == 'POST' && req.url.path == '/v1/auth/code') {
return http.Response('', 204);
}
return http.Response(jsonEncode({'message_zh': 'unexpected'}), 404);
});
final api = AuthApi(baseUrl: 'http://test.invalid', client: mockClient);
await tester.pumpWidget(ProviderScope(
overrides: [tokenStoreProvider.overrideWithValue(const _NullTokenStore())],
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: PangolinTheme.light,
home: AuthScreen(t: t, onDone: () {}, api: api),
),
));
await tester.pumpAndSettle();
// 切到注册 tab。
await tester.tap(find.text(t.tabRegister));
await tester.pumpAndSettle();
// step0:填邮箱 → 点发送验证码(mock 204)。
await tester.enterText(find.byType(TextField).first, 'invitee@example.com');
await tester.pump();
await tester.tap(find.text(t.sendCode));
await tester.pumpAndSettle();
// 填 6 位验证码 → 下一步,进 step1。
await tester.enterText(find.byType(TextField).at(1), '123456');
await tester.pump();
await tester.tap(find.text(t.doNext));
await tester.pumpAndSettle();
}
testWidgets('注册 step1(设密码步)渲染邀请码(选填)输入框', (tester) async {
await _driveToRegisterStep1(tester);
expect(find.byKey(const Key('invite-code-field')), findsOneWidget);
expect(find.text(t.inviteCodeFieldLabel), findsOneWidget);
});
testWidgets('登录路径不含邀请码输入框', (tester) async {
final api = AuthApi(baseUrl: 'http://test.invalid', client: MockClient((req) async => http.Response('', 404)));
await tester.pumpWidget(ProviderScope(
overrides: [tokenStoreProvider.overrideWithValue(const _NullTokenStore())],
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: PangolinTheme.light,
home: AuthScreen(t: t, onDone: () {}, api: api),
),
));
await tester.pumpAndSettle();
expect(find.byKey(const Key('invite-code-field')), findsNothing);
});
}