import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:userfront/features/profile/data/models/user_profile_model.dart'; import 'package:userfront/features/profile/domain/notifiers/profile_notifier.dart'; import 'package:userfront/features/profile/presentation/pages/profile_page.dart'; // Mocking the profile notifier class MockProfileNotifier extends ProfileNotifier { UserProfile? _profile; bool updateCalled = false; String? updatedName; @override Future build() async { _profile = UserProfile( id: 'test-id', email: 'test@example.com', name: 'Original Name', phone: '01012345678', department: 'Dev', affiliationType: 'employee', companyCode: 'C100', ); return _profile; } @override Future loadProfile() async { state = const AsyncValue.loading(); state = AsyncValue.data(_profile); return _profile; } @override Future updateProfile({ String? name, String? phone, String? department, }) async { updateCalled = true; updatedName = name; _profile = _profile!.copyWith( name: name ?? _profile!.name, phone: phone ?? _profile!.phone, department: department ?? _profile!.department, ); state = AsyncValue.data(_profile); } } void main() { testWidgets( 'ProfilePage explicit save button UX flow (Edit -> Cancel -> Edit -> Save)', (tester) async { final recordedErrors = []; final previousOnError = FlutterError.onError; FlutterError.onError = (details) { final text = details.exceptionAsString(); if (text.contains('A RenderFlex overflowed')) { return; } recordedErrors.add(details); }; addTearDown(() { FlutterError.onError = previousOnError; }); tester.view.physicalSize = const Size(1920, 1080); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final mockNotifier = MockProfileNotifier(); await tester.pumpWidget( ProviderScope( overrides: [profileProvider.overrideWith(() => mockNotifier)], child: const MaterialApp(home: Scaffold(body: ProfilePage())), ), ); await tester.pumpAndSettle(); // 1. Entering edit mode final editButton = find.byKey(const Key('profile-name-edit-button')); expect(editButton, findsOneWidget); await tester.tap(editButton); await tester.pumpAndSettle(); final inputField = find.byKey(const Key('profile-name-input')); expect(inputField, findsOneWidget); // 2. Testing cancel flow await tester.enterText(inputField, 'Changed Name'); await tester.pumpAndSettle(); final cancelButton = find.byKey(const Key('profile-name-cancel-button')); await tester.tap(cancelButton); await tester.pumpAndSettle(); // After cancellation, the field should be read-only again. expect(find.byKey(const Key('profile-name-input')), findsNothing); // Find text could be part of ListTile expect(find.text('Original Name'), findsWidgets); // 3. Re-enter edit mode and explicitly save await tester.tap(find.byKey(const Key('profile-name-edit-button'))); await tester.pumpAndSettle(); await tester.enterText( find.byKey(const Key('profile-name-input')), 'Saved Name', ); await tester.pumpAndSettle(); final saveButton = find.byKey(const Key('profile-name-save-button')); await tester.tap(saveButton); await tester.pumpAndSettle(); await tester.pump(const Duration(seconds: 4)); await tester.pumpAndSettle(); FlutterError.onError = previousOnError; // Verify the mock received the update expect(mockNotifier.updateCalled, isTrue); expect(mockNotifier.updatedName, 'Saved Name'); expect(recordedErrors, isEmpty); }, ); }