diff --git a/lib/screens/kyc/cubits/kyc/kyc_cubit.dart b/lib/screens/kyc/cubits/kyc/kyc_cubit.dart index fe363b0f..5bbb5018 100644 --- a/lib/screens/kyc/cubits/kyc/kyc_cubit.dart +++ b/lib/screens/kyc/cubits/kyc/kyc_cubit.dart @@ -45,11 +45,22 @@ class KycCubit extends Cubit { Future checkKyc({String? context}) async { _kycContext = context ?? _kycContext; + // The merge-processing waiting screen must survive a slow refresh: the + // backend may still be re-parenting the merge when the user taps refresh, + // and surfacing the watchdog timeout as KycFailure would route them to the + // error screen — the exact failure mode the waiting page exists to avoid. + // Captured before _runCheckKyc, which immediately replaces the state with + // KycLoading. + final wasMergeProcessing = state is KycMergeProcessing; final generation = ++_runGeneration; try { await _runCheckKyc(generation).timeout(_checkKycTimeout); } on TimeoutException { if (isClosed || generation != _runGeneration) return; + if (wasMergeProcessing) { + emit(const KycMergeProcessing()); + return; + } emit(const KycFailure('KYC backend did not respond in time')); } catch (e) { if (isClosed || generation != _runGeneration) return; diff --git a/test/goldens/screens/home/goldens/macos/home_page_loaded.png b/test/goldens/screens/home/goldens/macos/home_page_loaded.png index 4dd0c568..84de9124 100644 Binary files a/test/goldens/screens/home/goldens/macos/home_page_loaded.png and b/test/goldens/screens/home/goldens/macos/home_page_loaded.png differ diff --git a/test/goldens/screens/kyc/goldens/macos/kyc_merge_processing_page_default.png b/test/goldens/screens/kyc/goldens/macos/kyc_merge_processing_page_default.png new file mode 100644 index 00000000..1da6cee3 Binary files /dev/null and b/test/goldens/screens/kyc/goldens/macos/kyc_merge_processing_page_default.png differ diff --git a/test/goldens/screens/kyc/kyc_merge_processing_golden_test.dart b/test/goldens/screens/kyc/kyc_merge_processing_golden_test.dart new file mode 100644 index 00000000..d164c8a3 --- /dev/null +++ b/test/goldens/screens/kyc/kyc_merge_processing_golden_test.dart @@ -0,0 +1,36 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/screens/kyc/cubits/kyc/kyc_cubit.dart'; +import 'package:realunit_wallet/screens/kyc/subpages/kyc_merge_processing_page.dart'; + +import '../../../helper/helper.dart'; + +class _MockKycCubit extends MockCubit implements KycCubit {} + +void main() { + late _MockKycCubit kycCubit; + + setUp(() { + kycCubit = _MockKycCubit(); + when(() => kycCubit.state).thenReturn(const KycInitial()); + }); + + group('$KycMergeProcessingPage', () { + goldenTest( + 'default state', + fileName: 'kyc_merge_processing_page_default', + constraints: phoneConstraints, + // The page shows a CupertinoActivityIndicator (an endless animation), so + // pumpAndSettle would never settle — pump a single frame instead. + pumpBeforeTest: pumpOnce, + builder: () => wrapForGolden( + BlocProvider.value( + value: kycCubit, + child: const KycMergeProcessingPage(), + ), + ), + ); + }); +} diff --git a/test/screens/kyc/cubits/kyc/kyc_cubit_test.dart b/test/screens/kyc/cubits/kyc/kyc_cubit_test.dart index 6752dcf6..9aabea0a 100644 --- a/test/screens/kyc/cubits/kyc/kyc_cubit_test.dart +++ b/test/screens/kyc/cubits/kyc/kyc_cubit_test.dart @@ -919,6 +919,50 @@ void main() { }); }, ); + + // A slow refresh from the merge-processing waiting screen must NOT route the + // user to the error screen: when the watchdog fires and the previous state + // was KycMergeProcessing, the cubit returns to KycMergeProcessing instead of + // emitting KycFailure. Contrast with the watchdog test above (non-merge + // state -> KycFailure), which still holds. + test( + 'stays on KycMergeProcessing instead of KycFailure when a refresh exceeds the timeout', + () { + fakeAsync((async) { + // Phase 1: backend reports MergeProcessing -> cubit reaches the waiting state. + when(() => kycService.getKycStatus()).thenAnswer( + (_) async => _kycStatus( + level: KycLevel.level20, + processStatus: KycProcessStatus.mergeProcessing, + ), + ); + when(() => kycService.getUser()).thenAnswer((_) async => _user()); + + final cubit = buildCubit(); + final states = []; + final sub = cubit.stream.listen(states.add); + + cubit.markLegalDisclaimerAccepted(); + unawaited(cubit.checkKyc()); + async.flushMicrotasks(); + + // Phase 2: refresh while the backend hangs past the 30s watchdog. + when(() => kycService.getKycStatus()).thenAnswer((_) => Completer().future); + unawaited(cubit.checkKyc()); + async.elapse(const Duration(seconds: 31)); + + expect(states, [ + const KycLoading(), + const KycMergeProcessing(), + const KycLoading(), + const KycMergeProcessing(), + ]); + + sub.cancel(); + cubit.close(); + }); + }, + ); }); group('$KycCubit context forwarding', () {