// 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 saveTokens({required String access, required String refresh}) async {} @override Future loadAccessToken() async => null; @override Future loadRefreshToken() async => null; @override Future clear() async {} @override Future markOnboarded() async {} @override Future isOnboarded() async => true; @override Future saveLastEmail(String email) async {} @override Future loadLastEmail() async => null; } void main() { setUpAll(disableGoogleFontsFetching); const t = StringsZh(); Future _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); }); }