diff --git a/lib/views/after_auth_screens/funds/campaigns_screen.dart b/lib/views/after_auth_screens/funds/campaigns_screen.dart new file mode 100644 index 000000000..b9f78bb0b --- /dev/null +++ b/lib/views/after_auth_screens/funds/campaigns_screen.dart @@ -0,0 +1 @@ +// TODO Implement this library. \ No newline at end of file diff --git a/lib/views/after_auth_screens/funds/fund_pledges_screen.dart b/lib/views/after_auth_screens/funds/fund_pledges_screen.dart index cd791ddd7..d7985f866 100644 --- a/lib/views/after_auth_screens/funds/fund_pledges_screen.dart +++ b/lib/views/after_auth_screens/funds/fund_pledges_screen.dart @@ -303,7 +303,6 @@ class _PledgesScreenState extends State { /// **returns**: /// * `Widget`: a list view of all pledges. Widget _buildPledgesList(FundViewModel model) { - print(model.filteredPledges.length); if (model.isFetchingPledges) { return const Center(child: CircularProgressIndicator()); } @@ -326,7 +325,6 @@ class _PledgesScreenState extends State { return ListView.builder( itemCount: model.filteredPledges.length, itemBuilder: (context, index) { - print(model.filteredPledges.length); final pledge = model.filteredPledges[index]; return PledgeCard( pledge: pledge, diff --git a/lib/widgets/pledge_card.dart b/lib/widgets/pledge_card.dart index 06a9185af..d167fda49 100644 --- a/lib/widgets/pledge_card.dart +++ b/lib/widgets/pledge_card.dart @@ -125,7 +125,7 @@ class PledgeCard extends StatelessWidget { style: Theme.of(context).textTheme.bodySmall, ), Text( - '\$${pledge.amount?.toStringAsFixed(2) ?? 'N/A'}', + '\$${pledge.amount!.toStringAsFixed(2)}', style: Theme.of(context) .textTheme .titleLarge diff --git a/lib/widgets/update_pledge_dialogue_box.dart b/lib/widgets/update_pledge_dialogue_box.dart index 65e27fe15..90909f5ba 100644 --- a/lib/widgets/update_pledge_dialogue_box.dart +++ b/lib/widgets/update_pledge_dialogue_box.dart @@ -44,6 +44,7 @@ class _UpdatePledgeDialogState extends State { super.initState(); _amountController = TextEditingController(text: widget.pledge.amount?.toString() ?? ''); + _amountController.addListener(_onAmountChanged); _startDate = widget.pledge.startDate; _endDate = widget.pledge.endDate; _selectedPledgers = widget.pledge.pledgers ?? []; @@ -56,6 +57,24 @@ class _UpdatePledgeDialogState extends State { _originalEndDate = widget.pledge.endDate; } + /// Changes state if amout is changed. + /// + /// **params**: + /// None + /// + /// **returns**: + /// None + void _onAmountChanged() { + setState(() {}); + } + + @override + void dispose() { + _amountController.removeListener(_onAmountChanged); + _amountController.dispose(); + super.dispose(); + } + /// Checks if there are any changes in the current pledge details compared to the original values. /// /// **params**: @@ -64,7 +83,16 @@ class _UpdatePledgeDialogState extends State { /// **returns**: /// * `bool`: `true` if any detail has changed, `false` otherwise. bool _hasChanges() { - return _originalAmount != double.parse(_amountController.text) || + double currentAmount = _originalAmount; + if (_amountController.text.isNotEmpty) { + try { + currentAmount = double.tryParse(_amountController.text) ?? 0; + } catch (e) { + // Handle invalid input gracefully if necessary + } + } + + return currentAmount != _originalAmount || _originalCurrency != widget.model.donationCurrency || _originalPledgers.length != _selectedPledgers.length || _selectedPledgers.any((user) => !_originalPledgers.contains(user)) || @@ -72,6 +100,39 @@ class _UpdatePledgeDialogState extends State { _endDate != _originalEndDate; } + /// Method to get fields that are updated. + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `Map`: Object which include fields which are changed. + Map _getChangedFields() { + final Map changes = {'id': widget.pledge.id}; + try { + final double currentAmount = double.tryParse(_amountController.text) ?? 0; + if (currentAmount != _originalAmount) { + changes['amount'] = currentAmount; + } + } catch (e) { + // Handle parse error if needed + } + if (widget.model.donationCurrency != _originalCurrency) { + changes['currency'] = widget.model.donationCurrency; + } + if (_startDate != _originalStartDate && _startDate != null) { + changes['startDate'] = DateFormat('yyyy-MM-dd').format(_startDate!); + } + if (_endDate != _originalEndDate && _endDate != null) { + changes['endDate'] = DateFormat('yyyy-MM-dd').format(_endDate!); + } + if (_selectedPledgers.length != _originalPledgers.length || + _selectedPledgers.any((user) => !_originalPledgers.contains(user))) { + changes['users'] = _selectedPledgers.map((user) => user.id).toList(); + } + return changes; + } + @override Widget build(BuildContext context) { return AlertDialog( @@ -177,6 +238,7 @@ class _UpdatePledgeDialogState extends State { ), Expanded( child: TextFormField( + key: const Key('amount_field'), controller: _amountController, decoration: InputDecoration( labelText: 'Amount', @@ -187,7 +249,15 @@ class _UpdatePledgeDialogState extends State { if (value == null || value.isEmpty) { return 'Please enter an amount'; } - return null; + final parsedValue = double.tryParse(value); + if (parsedValue == null) { + return 'Amount must be a number'; + } + if (parsedValue <= 0) { + return 'Amount must be greater than zero'; + } + + return null; // Input is valid }, ), ), @@ -252,22 +322,14 @@ class _UpdatePledgeDialogState extends State { child: const Text('Cancel'), ), ElevatedButton( + key: const Key('update_btn'), onPressed: _hasChanges() ? () { if (_formKey.currentState!.validate() && _startDate != null && _endDate != null && _selectedPledgers.isNotEmpty) { - widget.onSubmit({ - 'id': widget.pledge.id, - 'amount': double.parse(_amountController.text), - 'startDate': DateFormat('yyyy-MM-dd').format(_startDate!), - 'endDate': DateFormat('yyyy-MM-dd').format(_endDate!), - 'users': - _selectedPledgers.map((user) => user.id).toList(), - 'currency': widget.model.donationCurrency, - }); - Navigator.of(context).pop(); + widget.onSubmit(_getChangedFields()); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Please fill all fields')), diff --git a/test/views/after_auth_screens/funds/fund_pledges_screen_test.dart b/test/views/after_auth_screens/funds/fund_pledges_screen_test.dart new file mode 100644 index 000000000..79e25e3c3 --- /dev/null +++ b/test/views/after_auth_screens/funds/fund_pledges_screen_test.dart @@ -0,0 +1,252 @@ +// ignore_for_file: talawa_api_doc +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/models/funds/fund_campaign.dart'; +import 'package:talawa/models/funds/fund_pledges.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/fund_service.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/after_auth_view_models/funds_view_models/fund_view_model.dart'; +import 'package:talawa/views/after_auth_screens/funds/fund_pledges_screen.dart'; +import 'package:talawa/widgets/pledge_card.dart'; + +import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_locator.dart'; + +// Helper function to create test campaign +Campaign getTestCampaign() { + return Campaign( + id: "1", + name: "Test Campaign", + fundingGoal: 1000.0, + startDate: DateTime.now(), + endDate: DateTime.now().add(const Duration(days: 30)), + ); +} + +// Helper function to create test pledges +List getTestPledges() { + return [ + Pledge( + id: "1", + amount: 100, + endDate: DateTime.now().add(const Duration(days: 15)), + pledgers: [User(firstName: 'John', lastName: 'Doe')], + ), + Pledge( + id: "2", + amount: 200, + endDate: DateTime.now().add(const Duration(days: 20)), + pledgers: [User(firstName: 'Jane', lastName: 'Smith')], + ), + ]; +} + +Widget createPledgesScreen() { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: PledgesScreen(campaign: getTestCampaign()), + navigatorKey: locator().navigatorKey, + onGenerateRoute: router.generateRoute, + ); +} + +void main() { + testSetupLocator(); + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + + registerServices(); + locator().test(); + }); + + tearDownAll(() { + unregisterServices(); + }); + + group('PledgesScreen Widget Tests', () { + testWidgets('Check if PledgesScreen shows up', (tester) async { + await tester.pumpWidget(createPledgesScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(PledgesScreen), findsOneWidget); + expect(find.text('Pledges for Test Campaign'), findsOneWidget); + }); + + testWidgets('Test tab buttons functionality', (tester) async { + await tester.pumpWidget(createPledgesScreen()); + await tester.pumpAndSettle(); + + // Check if both tabs exist + expect(find.text('Pledged'), findsOneWidget); + expect(find.text('Raised'), findsOneWidget); + + // Test tab switching + await tester.tap(find.text('Raised')); + await tester.pumpAndSettle(); + + // Verify color change or selection state + final raisedButton = tester.widget( + find + .ancestor( + of: find.text('Raised'), + matching: find.byType(Container), + ) + .first, + ); + expect(raisedButton.decoration, isA()); + }); + + testWidgets('Test search functionality', (tester) async { + final mockFundViewModel = FundViewModel(); + // Setup mock data + mockFundViewModel.allPledges.addAll(getTestPledges()); + + await tester.pumpWidget(createPledgesScreen()); + await tester.pumpAndSettle(); + + // Find and interact with search field + final searchField = find.byType(TextField).first; + expect(searchField, findsOneWidget); + + await tester.enterText(searchField, 'John'); + await tester.pumpAndSettle(); + mockFundViewModel.allPledges.clear(); + }); + + testWidgets('Test sort dropdown functionality', (tester) async { + await tester.pumpWidget(createPledgesScreen()); + await tester.pumpAndSettle(); + + // Find dropdown + final dropdownButton = find.byType(DropdownButton); + expect(dropdownButton, findsOneWidget); + + // Open dropdown + await tester.tap(dropdownButton); + await tester.pumpAndSettle(); + + // Verify dropdown items + expect(find.text('End Date (Latest)'), findsWidgets); + expect(find.text('Amount (Highest)'), findsOneWidget); + }); + + testWidgets('Test add pledge functionality', (tester) async { + await tester.pumpWidget(createPledgesScreen()); + await tester.pumpAndSettle(); + + // Find and tap FAB + final fabButton = find.byType(FloatingActionButton); + expect(fabButton, findsOneWidget); + + await tester.tap(fabButton); + await tester.pumpAndSettle(); + + // Verify dialog appears + expect(find.text('Create Pledge'), findsOneWidget); + }); + + testWidgets('Test pledge list view when empty', (tester) async { + final mockFundViewModel = FundViewModel(); + // Setup empty pledges + mockFundViewModel.allPledges.clear(); + + await tester.pumpWidget(createPledgesScreen()); + await tester.pumpAndSettle(); + + expect(find.text('No pledges found.'), findsOneWidget); + }); + + testWidgets('Test progress indicator display', (tester) async { + final mockFundViewModel = FundViewModel(); + // Setup mock data with specific values + mockFundViewModel.allPledges.addAll(getTestPledges()); + + await tester.pumpWidget(createPledgesScreen()); + await tester.pumpAndSettle(); + + // Verify progress bar elements + expect(find.byType(Stack), findsWidgets); + expect(find.textContaining('%'), findsOneWidget); + expect(find.textContaining('Goal: \$'), findsOneWidget); + }); + + testWidgets('Test pledge card interactions', (tester) async { + final mockFundService = locator(); + final mockPledges = [ + Pledge( + id: '1', + pledgers: [ + User( + id: userConfig.currentUser.id, + firstName: 'John', + lastName: 'Doe', + ), + ], + amount: 100, + ), + ]; + + when(mockFundService.getPledgesByCampaign('1', orderBy: 'endDate_DESC')) + .thenAnswer((_) async => mockPledges); + await tester.pumpWidget(createPledgesScreen()); + await tester.pumpAndSettle(); + + // Find pledge cards + expect(find.byType(PledgeCard), findsWidgets); + + // Test update pledge + await tester.tap(find.byIcon(Icons.edit).first); + await tester.pumpAndSettle(); + + // Verify update dialog appears + expect(find.text('Update Pledge'), findsOneWidget); + }); + + testWidgets('Test delete pledge confirmation', (tester) async { + final mockFundService = locator(); + final mockPledges = [ + Pledge( + id: '1', + pledgers: [ + User( + id: userConfig.currentUser.id, + firstName: 'John', + lastName: 'Doe', + ), + ], + amount: 100, + ), + ]; + + when(mockFundService.getPledgesByCampaign('1', orderBy: 'endDate_DESC')) + .thenAnswer((_) async => mockPledges); + + await tester.pumpWidget(createPledgesScreen()); + await tester.pumpAndSettle(); + + // Find and tap delete button + await tester.tap(find.byIcon(Icons.delete).first); + await tester.pumpAndSettle(); + + // Verify delete confirmation dialog + expect(find.text('Delete Pledge'), findsOneWidget); + expect( + find.text('Are you sure you want to delete this pledge?'), + findsOneWidget, + ); + expect(find.text('Cancel'), findsOneWidget); + expect(find.text('Delete'), findsWidgets); + }); + }); +} diff --git a/test/views/after_auth_screens/funds/fund_screen_test.dart b/test/views/after_auth_screens/funds/fund_screen_test.dart new file mode 100644 index 000000000..ba43296e1 --- /dev/null +++ b/test/views/after_auth_screens/funds/fund_screen_test.dart @@ -0,0 +1,142 @@ +// ignore_for_file: talawa_api_doc +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/models/funds/fund.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/fund_service.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/views/after_auth_screens/funds/fundraising_campaigns_screen.dart'; +import 'package:talawa/views/after_auth_screens/funds/funds_screen.dart'; + +import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_locator.dart'; + +Widget createFundScreen() { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: const FundScreen(), + navigatorKey: locator().navigatorKey, + onGenerateRoute: router.generateRoute, + ); +} + +void main() { + testSetupLocator(); + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + registerServices(); + locator().test(); + }); + + tearDownAll(() { + unregisterServices(); + }); + + group('FundScreen Widget Tests', () { + testWidgets('Check if FundScreen shows up', (tester) async { + await tester.pumpWidget(createFundScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(FundScreen), findsOneWidget); + expect(find.text('Funds'), findsOneWidget); + }); + + testWidgets('Test search functionality', (tester) async { + final mockFundService = locator(); + final mockFunds = [ + Fund( + id: '1', + name: 'Test Fund', + creator: User(firstName: 'John', lastName: 'Doe'), + ), + Fund( + id: '2', + name: 'Fund 2', + creator: User(firstName: 'John1', lastName: 'Doe2'), + ), + ]; + when(mockFundService.getFunds(orderBy: 'createdAt_DESC')) + .thenAnswer((_) async => mockFunds); + + await tester.pumpWidget(createFundScreen()); + await tester.pumpAndSettle(); + + // Find and interact with search field + final searchField = find.byType(TextField).first; + expect(searchField, findsOneWidget); + + await tester.enterText(searchField, 'Test'); + await tester.pumpAndSettle(); + + // Verify that the filtered fund is displayed + expect(find.text('Test Fund'), findsOneWidget); + expect(find.text('Fund 2'), findsNothing); + }); + + testWidgets('Test sort dropdown functionality', (tester) async { + await tester.pumpWidget(createFundScreen()); + await tester.pumpAndSettle(); + + // Find dropdown + final dropdownButton = find.byType(DropdownButton); + expect(dropdownButton, findsOneWidget); + + // Open dropdown + await tester.tap(dropdownButton); + await tester.pumpAndSettle(); + + // Verify dropdown items + expect(find.text('Newest'), findsWidgets); + expect(find.text('Oldest'), findsOneWidget); + }); + + testWidgets('Test fund list view when empty', (tester) async { + final mockFundService = locator(); + when(mockFundService.getFunds(orderBy: 'createdAt_DESC')) + .thenAnswer((_) async => []); + await tester.pumpWidget(createFundScreen()); + await tester.pumpAndSettle(); + + expect(find.text('No funds in this organization.'), findsOneWidget); + }); + + testWidgets('Test fund card interactions', (tester) async { + final mockFundService = locator(); + final mockFund = Fund( + id: '1', + name: 'Test Fund', + creator: User(firstName: 'John', lastName: 'Doe'), + createdAt: DateTime.now(), + ); + + when( + mockFundService.getFunds( + orderBy: 'createdAt_DESC', + ), + ).thenAnswer((_) async => [mockFund]); + + await tester.pumpWidget(createFundScreen()); + await tester.pumpAndSettle(); + + // Find fund cards + expect(find.byType(FundCard), findsWidgets); + + // Test navigate to campaigns screen + await tester.tap(find.byIcon(Icons.campaign).first); + await tester.pumpAndSettle(); + + // Verify campaigns screen is displayed + expect(find.byType(CampaignsScreen), findsOneWidget); + }); + }); +} diff --git a/test/views/after_auth_screens/funds/fundraising_campaigns_screen_test.dart b/test/views/after_auth_screens/funds/fundraising_campaigns_screen_test.dart new file mode 100644 index 000000000..d4f8521ed --- /dev/null +++ b/test/views/after_auth_screens/funds/fundraising_campaigns_screen_test.dart @@ -0,0 +1,146 @@ +// ignore_for_file: talawa_api_doc +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/models/funds/fund_campaign.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/fund_service.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/views/after_auth_screens/funds/fund_pledges_screen.dart'; +import 'package:talawa/views/after_auth_screens/funds/fundraising_campaigns_screen.dart'; + +import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_locator.dart'; + +Widget createCampaignsScreen() { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: const CampaignsScreen( + fundId: '1', + fundName: 'Test Fund', + ), + navigatorKey: locator().navigatorKey, + onGenerateRoute: router.generateRoute, + ); +} + +void main() { + testSetupLocator(); + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + registerServices(); + locator().test(); + }); + + tearDownAll(() { + unregisterServices(); + }); + + group('CampaignsScreen Widget Tests', () { + testWidgets('Check if CampaignsScreen shows up', (tester) async { + await tester.pumpWidget(createCampaignsScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(CampaignsScreen), findsOneWidget); + expect(find.text('Campaigns'), findsOneWidget); + }); + + testWidgets('Test search functionality', (tester) async { + final mockFundService = locator(); + final mockCampaign = [ + Campaign( + id: '1', + name: 'Test Campaign', + startDate: DateTime.now(), + endDate: DateTime.now().add(const Duration(days: 30)), + fundingGoal: 1000.0, + ), + ]; + when(mockFundService.getCampaigns('1', orderBy: 'endDate_DESC')) + .thenAnswer((_) async => mockCampaign); + + await tester.pumpWidget(createCampaignsScreen()); + await tester.pumpAndSettle(); + + // Find and interact with search field + final searchField = find.byType(TextField).first; + expect(searchField, findsOneWidget); + + await tester.enterText(searchField, 'Test'); + await tester.pumpAndSettle(); + + // Verify that the filtered campaign is displayed + expect(find.text('Test Campaign'), findsOneWidget); + }); + + testWidgets('Test sort dropdown functionality', (tester) async { + await tester.pumpWidget(createCampaignsScreen()); + await tester.pumpAndSettle(); + + // Find dropdown + final dropdownButton = find.byType(DropdownButton); + expect(dropdownButton, findsOneWidget); + + // Open dropdown + await tester.tap(dropdownButton); + await tester.pumpAndSettle(); + + // Verify dropdown items + expect(find.text('End Date (Latest)'), findsWidgets); + expect(find.text('End Date (Earliest)'), findsOneWidget); + expect(find.text('Amount (Highest)'), findsOneWidget); + expect(find.text('Amount (Lowest)'), findsOneWidget); + }); + + testWidgets('Test campaign list view when empty', (tester) async { + final mockFundService = locator(); + + when(mockFundService.getCampaigns('1', orderBy: 'endDate_DESC')) + .thenAnswer((_) async => []); + + await tester.pumpWidget(createCampaignsScreen()); + await tester.pumpAndSettle(); + + expect(find.text('No campaigns for this fund.'), findsOneWidget); + }); + + testWidgets('Test campaign card interactions', (tester) async { + final mockFundService = locator(); + final mockCampaign = Campaign( + id: '1', + name: 'Test Campaign', + startDate: DateTime.now(), + endDate: DateTime.now().add(const Duration(days: 30)), + fundingGoal: 1000.0, + ); + + when( + mockFundService.getCampaigns( + '1', + orderBy: 'endDate_DESC', + ), + ).thenAnswer((_) async => [mockCampaign]); + + await tester.pumpWidget(createCampaignsScreen()); + await tester.pumpAndSettle(); + + // Find campaign cards + expect(find.byType(CampaignCard), findsWidgets); + + // Test navigate to pledges screen + await tester.tap(find.byIcon(Icons.volunteer_activism).first); + await tester.pumpAndSettle(); + + // Verify pledges screen is displayed + expect(find.byType(PledgesScreen), findsOneWidget); + }); + }); +} diff --git a/test/widget_tests/widgets/add_pledge_dialogue_box_test.dart b/test/widget_tests/widgets/add_pledge_dialogue_box_test.dart new file mode 100644 index 000000000..9acaa63fb --- /dev/null +++ b/test/widget_tests/widgets/add_pledge_dialogue_box_test.dart @@ -0,0 +1,286 @@ +// ignore_for_file: talawa_api_doc +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/after_auth_view_models/funds_view_models/fund_view_model.dart'; +import 'package:talawa/widgets/add_pledge_dialogue_box.dart'; + +class MockFundViewModel extends Mock implements FundViewModel { + @override + String get donationCurrency => 'USD'; + + @override + String get donationCurrencySymbol => '\$'; + + @override + List get orgMembersList => [ + User( + id: '1', + firstName: 'John', + lastName: 'Doe', + ), + User( + id: '2', + firstName: 'Jane', + lastName: 'Smith', + ), + User( + id: '3', + firstName: 'Bob', + lastName: 'Johnson', + ), + ]; +} + +Widget createAddPledgeDialog({ + required Function(Map) onSubmit, + required FundViewModel model, + required String campaignId, +}) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: Scaffold( + body: Builder( + builder: (context) => TextButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => AddPledgeDialog( + onSubmit: onSubmit, + model: model, + campaignId: campaignId, + ), + ); + }, + child: const Text('Show Dialog'), + ), + ), + ), + ); +} + +void main() { + late MockFundViewModel mockModel; + late Map submittedData; + + setUp(() { + mockModel = MockFundViewModel(); + submittedData = {}; + }); + + group('AddPledgeDialog Widget Tests', () { + testWidgets('Dialog shows up with correct initial state', + (WidgetTester tester) async { + await tester.pumpWidget( + createAddPledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + campaignId: '123', + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Verify initial state + expect(find.text('Create Pledge'), findsOneWidget); + expect(find.text('Select Pledger:'), findsOneWidget); + expect(find.text('Select Start date'), findsOneWidget); + expect(find.text('Select End date'), findsOneWidget); + expect(find.text('Amount'), findsOneWidget); + expect(find.text('USD'), findsOneWidget); + }); + + testWidgets('Can select pledgers from popup menu', + (WidgetTester tester) async { + await tester.pumpWidget( + createAddPledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + campaignId: '123', + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Open pledger selection menu + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + + // Select a pledger + await tester.tap(find.text('John Doe').last); + await tester.pumpAndSettle(); + + // Verify pledger chip appears + expect(find.byType(Chip), findsOneWidget); + expect(find.text('John Doe'), findsOneWidget); + }); + + testWidgets('Can remove selected pledgers', (WidgetTester tester) async { + await tester.pumpWidget( + createAddPledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + campaignId: '123', + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Add a pledger + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + await tester.tap(find.text('John Doe').last); + await tester.pumpAndSettle(); + + // Remove the pledger + await tester.tap(find.byIcon(Icons.cancel)); + await tester.pumpAndSettle(); + + // Verify pledger is removed + expect(find.byType(Chip), findsNothing); + }); + + testWidgets('Can select dates', (WidgetTester tester) async { + await tester.pumpWidget( + createAddPledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + campaignId: '123', + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Select start date + await tester.tap(find.text('Select Start date')); + await tester.pumpAndSettle(); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Verify start date is selected + expect(find.text('Select Start date'), findsNothing); + expect(find.textContaining('Start:'), findsOneWidget); + + // Select end date + await tester.tap(find.text('Select End date')); + await tester.pumpAndSettle(); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Verify end date is selected + expect(find.text('Select End date'), findsNothing); + expect(find.textContaining('End:'), findsOneWidget); + }); + + testWidgets('Form validation works correctly', (WidgetTester tester) async { + await tester.pumpWidget( + createAddPledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + campaignId: '123', + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Try to submit empty form + await tester.tap(find.text('Create')); + await tester.pumpAndSettle(); + + // Verify error message + expect(find.text('Please fill all fields'), findsOneWidget); + }); + + testWidgets('Can submit valid form', (WidgetTester tester) async { + await tester.pumpWidget( + createAddPledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + campaignId: '123', + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Fill form + // Add pledger + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + await tester.tap(find.text('John Doe').last); + await tester.pumpAndSettle(); + + // Enter amount + await tester.enterText(find.byType(TextFormField), '100'); + + // Select dates + await tester.tap(find.text('Select Start date')); + await tester.pumpAndSettle(); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Select End date')); + await tester.pumpAndSettle(); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Submit form + await tester.tap(find.text('Create')); + await tester.pumpAndSettle(); + + // Verify form submission + expect(submittedData.isEmpty, false); + expect(submittedData['campaignId'], '123'); + expect(submittedData['amount'], 100.0); + expect(submittedData['userIds'], ['1']); + expect(submittedData['currency'], 'USD'); + }); + + testWidgets('Can cancel dialog', (WidgetTester tester) async { + await tester.pumpWidget( + createAddPledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + campaignId: '123', + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Cancel dialog + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + + // Verify dialog is closed + expect(find.text('Create Pledge'), findsNothing); + }); + }); +} diff --git a/test/widget_tests/widgets/pledge_card_test.dart b/test/widget_tests/widgets/pledge_card_test.dart new file mode 100644 index 000000000..e06cfc8b5 --- /dev/null +++ b/test/widget_tests/widgets/pledge_card_test.dart @@ -0,0 +1,179 @@ +// ignore_for_file: talawa_api_doc +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/intl.dart'; +import 'package:talawa/models/funds/fund_pledges.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/widgets/pledge_card.dart'; + +import '../../helpers/test_helpers.dart'; +import '../../helpers/test_locator.dart'; + +Widget createPledgeCard({ + required Pledge pledge, + VoidCallback? onUpdate, + VoidCallback? onDelete, +}) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: Scaffold( + body: PledgeCard( + pledge: pledge, + onUpdate: onUpdate ?? () {}, + onDelete: onDelete ?? () {}, + ), + ), + ); +} + +final mockPledge = Pledge( + id: '1', + amount: 1000, + startDate: DateTime(2024, 1, 1), + endDate: DateTime(2024, 12, 31), + pledgers: [ + User( + id: '1', + firstName: 'John', + lastName: 'Doe', + ), + User( + id: '2', + firstName: 'Jane', + lastName: 'Smith', + ), + User( + id: '3', + firstName: 'Bob', + lastName: 'Johnson', + ), + User( + id: '4', + firstName: 'Alice', + lastName: 'Brown', + ), + ], +); + +void main() { + testSetupLocator(); + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + registerServices(); + locator().test(); + }); + + tearDownAll(() { + unregisterServices(); + }); + group('PledgeCard Widget Tests', () { + testWidgets('PledgeCard displays basic information correctly', + (WidgetTester tester) async { + await tester.pumpWidget(createPledgeCard(pledge: mockPledge)); + await tester.pumpAndSettle(); + + // Verify header + expect(find.text('Pledge Group'), findsOneWidget); + + // Verify amount information + expect(find.text('Pledged'), findsOneWidget); + expect(find.text('\$1000.00'), findsOneWidget); + expect(find.text('Donated'), findsOneWidget); + expect(find.text('\$0.00'), findsOneWidget); + + // Verify dates + expect(find.text('Start Date'), findsOneWidget); + expect(find.text('End Date'), findsOneWidget); + expect( + find.text(DateFormat('MMM d, y').format(DateTime(2024, 1, 1))), + findsOneWidget, + ); + expect( + find.text(DateFormat('MMM d, y').format(DateTime(2024, 12, 31))), + findsOneWidget, + ); + }); + + testWidgets('PledgeCard displays correct number of pledgers', + (WidgetTester tester) async { + await tester.pumpWidget(createPledgeCard(pledge: mockPledge)); + await tester.pumpAndSettle(); + + // Verify pledgers section + expect(find.text('Pledgers'), findsOneWidget); + + // Should show first 3 pledgers + expect(find.text('John Doe'), findsOneWidget); + expect(find.text('Jane Smith'), findsOneWidget); + expect(find.text('Bob Johnson'), findsOneWidget); + + // Should show +1 more chip for the remaining pledger + expect(find.text('+1 more'), findsOneWidget); + }); + + testWidgets('PledgeCard handles null dates correctly', + (WidgetTester tester) async { + final pledgeWithNullDates = Pledge( + id: '1', + amount: 1000, + startDate: null, + endDate: null, + pledgers: [], + ); + + await tester.pumpWidget(createPledgeCard(pledge: pledgeWithNullDates)); + await tester.pumpAndSettle(); + + expect(find.text('N/A'), findsNWidgets(2)); + }); + + testWidgets('PledgeCard handles empty pledgers list', + (WidgetTester tester) async { + final pledgeWithNoPledgers = Pledge( + id: '1', + amount: 1000, + startDate: DateTime.now(), + endDate: DateTime.now(), + pledgers: [], + ); + + await tester.pumpWidget(createPledgeCard(pledge: pledgeWithNoPledgers)); + await tester.pumpAndSettle(); + + expect(find.byType(Chip), findsNothing); + }); + + testWidgets('Update and Delete buttons trigger callbacks', + (WidgetTester tester) async { + bool updatePressed = false; + bool deletePressed = false; + + await tester.pumpWidget( + createPledgeCard( + pledge: mockPledge, + onUpdate: () => updatePressed = true, + onDelete: () => deletePressed = true, + ), + ); + await tester.pumpAndSettle(); + + // Test Update button + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + expect(updatePressed, true); + + // Test Delete button + await tester.tap(find.text('Delete')); + await tester.pumpAndSettle(); + expect(deletePressed, true); + }); + }); +} diff --git a/test/widget_tests/widgets/update_pledge_dialogue_box_test.dart b/test/widget_tests/widgets/update_pledge_dialogue_box_test.dart new file mode 100644 index 000000000..409a0233e --- /dev/null +++ b/test/widget_tests/widgets/update_pledge_dialogue_box_test.dart @@ -0,0 +1,652 @@ +// ignore_for_file: talawa_api_doc +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/models/funds/fund_pledges.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/after_auth_view_models/funds_view_models/fund_view_model.dart'; +import 'package:talawa/widgets/update_pledge_dialogue_box.dart'; + +class MockFundViewModel extends Mock implements FundViewModel { + @override + String get donationCurrency => 'USD'; + + @override + String get donationCurrencySymbol => '\$'; + + @override + List get orgMembersList => [ + User( + id: '1', + firstName: 'John', + lastName: 'Doe', + ), + User( + id: '2', + firstName: 'Jane', + lastName: 'Smith', + ), + User( + id: '3', + firstName: 'Bob', + lastName: 'Johnson', + ), + ]; +} + +Widget createUpdatePledgeDialog({ + required Function(Map) onSubmit, + required FundViewModel model, + required Pledge pledge, +}) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: Scaffold( + body: Builder( + builder: (context) => TextButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => UpdatePledgeDialog( + onSubmit: onSubmit, + model: model, + pledge: pledge, + ), + ); + }, + child: const Text('Show Dialog'), + ), + ), + ), + ); +} + +void main() { + late MockFundViewModel mockModel; + late Map submittedData; + late Pledge testPledge; + + setUp(() { + mockModel = MockFundViewModel(); + submittedData = {}; + testPledge = Pledge( + id: '123', + amount: 100, + startDate: DateTime(2024, 1, 1), + endDate: DateTime(2024, 12, 31), + pledgers: [ + User( + id: '1', + firstName: 'John', + lastName: 'Doe', + ), + ], + ); + }); + + group('UpdatePledgeDialog Widget Tests', () { + testWidgets('Dialog shows up with correct initial state', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Verify initial state + expect(find.text('Update Pledge'), findsOneWidget); + expect(find.text('Select Pledger:'), findsOneWidget); + expect(find.text('100'), findsOneWidget); + expect(find.text('USD'), findsOneWidget); + expect(find.text('John Doe'), findsOneWidget); + expect(find.textContaining('Start: Jan 1, 2024'), findsOneWidget); + expect(find.textContaining('End: Dec 31, 2024'), findsOneWidget); + }); + + testWidgets('Can add new pledgers from popup menu', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Open pledger selection menu + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + + // Select a new pledger + await tester.tap(find.text('Jane Smith').last); + await tester.pumpAndSettle(); + + // Verify both pledgers are shown + expect(find.text('John Doe'), findsOneWidget); + expect(find.text('Jane Smith'), findsOneWidget); + expect(find.byType(Chip), findsNWidgets(2)); + }); + + testWidgets('Can remove pledgers', (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Remove existing pledger + await tester.tap(find.byIcon(Icons.cancel)); + await tester.pumpAndSettle(); + + // Verify pledger is removed + expect(find.byType(Chip), findsNothing); + }); + + testWidgets('Can update dates', (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Update start date + await tester.tap(find.textContaining('Start:')); + await tester.pumpAndSettle(); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Update end date + await tester.tap(find.textContaining('End:')); + await tester.pumpAndSettle(); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Verify dates are updated + expect(find.textContaining('Start:'), findsOneWidget); + expect(find.textContaining('End:'), findsOneWidget); + }); + + testWidgets('Update button is disabled when no changes are made', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Verify update button is disabled + final updateButton = find.text('Update'); + expect( + tester + .widget( + find.ancestor( + of: updateButton, + matching: find.byType(ElevatedButton), + ), + ) + .onPressed, + null, + ); + }); + + testWidgets('Update button is enabled when changes are made', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Verify initial state - update button should be disabled + final initialUpdateButton = tester.widget( + find.byKey(const Key('update_btn')), + ); + expect(initialUpdateButton.onPressed, null); + + // Change amount + await tester.enterText(find.byKey(const Key('amount_field')), '200'); + await tester.pumpAndSettle(); + + // Verify initial state - update button should be disabled + final updateButton = tester.widget( + find.byKey(const Key('update_btn')), + ); + expect(updateButton.onPressed, isNotNull); + }); + + testWidgets('Can submit valid form with updates', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Update amount + await tester.enterText(find.byType(TextFormField), '200'); + await tester.pumpAndSettle(); + + // Add new pledger + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + await tester.tap(find.text('Jane Smith').last); + await tester.pumpAndSettle(); + + // Submit form + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + + // Verify form submission + expect(submittedData.isEmpty, false); + expect(submittedData['id'], '123'); + expect(submittedData['amount'], 200.0); + expect(submittedData['users'], ['1', '2']); + }); + + testWidgets('Can cancel dialog', (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Cancel dialog + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + + // Verify dialog is closed + expect(find.text('Update Pledge'), findsNothing); + }); + + testWidgets('Shows error message when form is invalid', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Remove all pledgers + await tester.tap(find.byIcon(Icons.cancel)); + await tester.pumpAndSettle(); + + // Clear amount + await tester.enterText(find.byType(TextFormField), ''); + await tester.pumpAndSettle(); + + // Try to submit + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + + // Verify error message + expect(find.text('Please fill all fields'), findsOneWidget); + }); + + group('_getChangedFields Tests', () { + testWidgets('Returns only id when no changes are made', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Verify only id is in the changed fields + expect(submittedData['id'], '123'); + expect(submittedData.length, 1); + }); + + testWidgets('Correctly identifies changed amount', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Change amount + await tester.enterText(find.byKey(const Key('amount_field')), '200'); + await tester.pumpAndSettle(); + + // Submit form + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + + // Verify changed fields + expect(submittedData['id'], '123'); + expect(submittedData['amount'], 200.0); + expect(submittedData.containsKey('currency'), false); + expect(submittedData.containsKey('startDate'), false); + expect(submittedData.containsKey('endDate'), false); + expect(submittedData.containsKey('users'), false); + }); + + testWidgets('Correctly identifies changed dates', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Change start date + await tester.tap(find.textContaining('Start:')); + await tester.pumpAndSettle(); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Change end date + await tester.tap(find.textContaining('End:')); + await tester.pumpAndSettle(); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Submit form + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + + // Verify changed fields include dates + expect(submittedData['id'], '123'); + expect(submittedData.containsKey('startDate'), true); + expect(submittedData.containsKey('endDate'), true); + expect(submittedData.containsKey('amount'), false); + expect(submittedData.containsKey('currency'), false); + expect(submittedData.containsKey('users'), false); + }); + + testWidgets('Correctly identifies changed pledgers', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Add new pledger + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + await tester.tap(find.text('Jane Smith').last); + await tester.pumpAndSettle(); + + // Submit form + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + + // Verify changed fields include users + expect(submittedData['id'], '123'); + expect(submittedData['users'], ['1', '2']); + expect(submittedData.containsKey('amount'), false); + expect(submittedData.containsKey('currency'), false); + expect(submittedData.containsKey('startDate'), false); + expect(submittedData.containsKey('endDate'), false); + }); + + testWidgets('Correctly identifies multiple changes', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Change amount + await tester.enterText(find.byKey(const Key('amount_field')), '200'); + await tester.pumpAndSettle(); + + // Add new pledger + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + await tester.tap(find.text('Jane Smith').last); + await tester.pumpAndSettle(); + + // Change dates + await tester.tap(find.textContaining('Start:')); + await tester.pumpAndSettle(); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + // Submit form + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + + // Verify all changed fields are included + expect(submittedData['id'], '123'); + expect(submittedData['amount'], 200.0); + expect(submittedData['users'], ['1', '2']); + expect(submittedData.containsKey('startDate'), true); + expect(submittedData.containsKey('currency'), false); + }); + + testWidgets('Handles invalid amount input gracefully', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Enter invalid amount + await tester.enterText( + find.byKey(const Key('amount_field')), + 'invalid', + ); + await tester.pumpAndSettle(); + + // Verify form validation prevents submission + expect(find.text('Please enter an amount'), findsOneWidget); + }); + group('_getChangedFields Tests', () { + testWidgets('Correctly identifies changed amount', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Change amount + await tester.enterText(find.byKey(const Key('amount_field')), '200'); + await tester.pumpAndSettle(); + + // Submit form + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + + // Verify changed fields + expect(submittedData['id'], '123'); + expect(submittedData['amount'], 200.0); + expect(submittedData.containsKey('currency'), false); + expect(submittedData.containsKey('startDate'), false); + expect(submittedData.containsKey('endDate'), false); + expect(submittedData.containsKey('users'), false); + }); + testWidgets('Correctly identifies changed pledgers', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Add new pledger + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + await tester.tap(find.text('Jane Smith').last); + await tester.pumpAndSettle(); + + // Submit form + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + + // Verify changed fields include users + expect(submittedData['id'], '123'); + expect(submittedData['users'], ['1', '2']); + expect(submittedData.containsKey('amount'), false); + expect(submittedData.containsKey('currency'), false); + expect(submittedData.containsKey('startDate'), false); + expect(submittedData.containsKey('endDate'), false); + }); + + testWidgets('Correctly identifies multiple changes', + (WidgetTester tester) async { + await tester.pumpWidget( + createUpdatePledgeDialog( + onSubmit: (data) => submittedData = data, + model: mockModel, + pledge: testPledge, + ), + ); + await tester.pumpAndSettle(); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Change amount + await tester.enterText(find.byKey(const Key('amount_field')), '200'); + await tester.pumpAndSettle(); + + // Add new pledger + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + await tester.tap(find.text('Jane Smith').last); + await tester.pumpAndSettle(); + + // Submit form + await tester.tap(find.text('Update')); + await tester.pumpAndSettle(); + + // Verify all changed fields are included + expect(submittedData['id'], '123'); + expect(submittedData['amount'], 200.0); + expect(submittedData['users'], ['1', '2']); + expect(submittedData.containsKey('currency'), false); + }); + }); + }); + }); +}