diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 1889ed52a..53414e5ad 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -13,7 +13,7 @@ name: PR Workflow on: pull_request: branches-ignore: - - 'master' + - "master" env: CODECOV_UNIQUE_NAME: CODECOV_UNIQUE_NAME-${{ github.run_id }}-${{ github.run_number }} @@ -30,12 +30,12 @@ jobs: fetch-depth: 0 - uses: actions/setup-java@v3 with: - distribution: 'zulu' # See 'Supported distributions' for available options - java-version: '12.0' + distribution: "zulu" # See 'Supported distributions' for available options + java-version: "12.0" - uses: subosito/flutter-action@v2 with: - flutter-version: '3.22.3' - channel: 'stable' # or: 'beta', 'dev' or 'master' + flutter-version: "3.22.3" + channel: "stable" # or: 'beta', 'dev' or 'master' - name: Set default branch. run: git remote set-head origin --auto shell: bash @@ -48,7 +48,7 @@ jobs: - name: Count lines of code in each file run: chmod +x ./.github/workflows/countline.py - name: Running count lines - run: ./.github/workflows/countline.py --exclude_directories test/ --exclude_files lib/custom_painters/talawa_logo.dart lib/custom_painters/language_icon.dart lib/custom_painters/whatsapp_logo.dart lib/utils/queries.dart lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart lib/view_model/pre_auth_view_models/select_organization_view_model.dart lib/views/after_auth_screens/profile/profile_page.dart lib/view_model/main_screen_view_model.dart lib/views/after_auth_screens/events/create_event_page.dart lib/views/after_auth_screens/org_info_screen.dart + run: ./.github/workflows/countline.py --exclude_directories test/ --exclude_files lib/custom_painters/talawa_logo.dart lib/custom_painters/language_icon.dart lib/custom_painters/whatsapp_logo.dart lib/utils/queries.dart lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart lib/view_model/pre_auth_view_models/select_organization_view_model.dart lib/views/after_auth_screens/profile/profile_page.dart lib/view_model/main_screen_view_model.dart lib/views/after_auth_screens/events/create_event_page.dart lib/views/after_auth_screens/org_info_screen.dart lib/views/after_auth_screens/events/manage_volunteer_group.dart - name: setup python uses: actions/setup-python@v5 - name: Check for presence of ignore directives corresponding to custom lints @@ -92,21 +92,21 @@ jobs: echo "Error: Source and Target Branches are the same. Please ensure they are different." exit 1 -# - name: Echo the GitHub environment for troubleshooting -# run: echo "$GITHUB_CONTEXT" -# - name: Echo the GitHub context for troubleshooting -# run: echo "${{ toJSON(github) }}" -# - name: setup python -# uses: actions/setup-python@v5 -# - name: Granting permission to documentationcheck.py -# run: chmod +x ./.github/workflows/documentationcheck.py -# - name: execute py script -# # For more information on the GitHub context used for the "--repository" flag used by this script visit: -# # https://docs.github.com/en/actions/learn-github-actions/contexts -# run: | -# git branch -# pip install GitPython -# python ./.github/workflows/documentationcheck.py --repository ${{github.repository}} --merge_branch_name ${{github.ref_name}} + # - name: Echo the GitHub environment for troubleshooting + # run: echo "$GITHUB_CONTEXT" + # - name: Echo the GitHub context for troubleshooting + # run: echo "${{ toJSON(github) }}" + # - name: setup python + # uses: actions/setup-python@v5 + # - name: Granting permission to documentationcheck.py + # run: chmod +x ./.github/workflows/documentationcheck.py + # - name: execute py script + # # For more information on the GitHub context used for the "--repository" flag used by this script visit: + # # https://docs.github.com/en/actions/learn-github-actions/contexts + # run: | + # git branch + # pip install GitPython + # python ./.github/workflows/documentationcheck.py --repository ${{github.repository}} --merge_branch_name ${{github.ref_name}} Flutter-Testing: name: Testing codebase @@ -116,12 +116,12 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-java@v3 with: - distribution: 'zulu' # See 'Supported distributions' for available options - java-version: '12.0' + distribution: "zulu" # See 'Supported distributions' for available options + java-version: "12.0" - uses: subosito/flutter-action@v2 with: - flutter-version: '3.22.3' - channel: 'stable' # or: 'beta', 'dev' or 'master' + flutter-version: "3.22.3" + channel: "stable" # or: 'beta', 'dev' or 'master' - name: Running pub get to fetch dependencies run: flutter pub get - name: Codebase testing @@ -132,11 +132,11 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} verbose: true fail_ci_if_error: false - name: '${{env.CODECOV_UNIQUE_NAME}}' + name: "${{env.CODECOV_UNIQUE_NAME}}" - name: Test acceptable level of code coverage uses: VeryGoodOpenSource/very_good_coverage@v2 with: - path: './coverage/lcov.info' + path: "./coverage/lcov.info" min_coverage: 92.0 Android-Build: @@ -147,12 +147,12 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-java@v3 with: - distribution: 'zulu' # See 'Supported distributions' for available options - java-version: '12.0' + distribution: "zulu" # See 'Supported distributions' for available options + java-version: "12.0" - uses: subosito/flutter-action@v2 with: - flutter-version: '3.22.3' - channel: 'stable' # or: 'beta', 'dev' or 'master' + flutter-version: "3.22.3" + channel: "stable" # or: 'beta', 'dev' or 'master' - name: Running pub get to fetch dependencies run: flutter pub get - name: Building for android @@ -166,8 +166,8 @@ jobs: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: - flutter-version: '3.22.3' - channel: 'stable' # or: 'beta', 'dev' or 'master' + flutter-version: "3.22.3" + channel: "stable" # or: 'beta', 'dev' or 'master' architecture: x64 - name: Building for ios run: flutter build ios --release --no-codesign diff --git a/lib/constants/routing_constants.dart b/lib/constants/routing_constants.dart index 389607c6e..912213978 100644 --- a/lib/constants/routing_constants.dart +++ b/lib/constants/routing_constants.dart @@ -114,4 +114,10 @@ class Routes { /// static variable to access org info screen. static const String orgInfoScreen = '/OrganisationInfoScreen'; + + ///static variable to access volunteer groups screen. + static const String volunteerGroupScreen = '/volunteerScreen'; + + ///static variable to access Manage volunteer group screen. + static const String manageVolunteerGroup = '/manageVolunteerScreen'; } diff --git a/lib/locator.dart b/lib/locator.dart index 26bd08347..c657f3961 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -30,6 +30,7 @@ import 'package:talawa/view_model/after_auth_view_models/event_view_models/edit_ import 'package:talawa/view_model/after_auth_view_models/event_view_models/event_calendar_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/manage_volunteer_group_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart'; @@ -149,7 +150,7 @@ Future setupLocator() async { locator.registerFactory(() => OrganizationFeedViewModel()); locator.registerFactory(() => SetUrlViewModel()); locator.registerFactory(() => LoginViewModel()); - + locator.registerFactory(() => ManageVolunteerGroupViewModel()); locator.registerFactory(() => SelectOrganizationViewModel()); locator.registerFactory(() => SignupDetailsViewModel()); locator.registerFactory(() => WaitingViewModel()); diff --git a/lib/models/events/event_volunteer.dart b/lib/models/events/event_volunteer.dart new file mode 100644 index 000000000..9a81c599c --- /dev/null +++ b/lib/models/events/event_volunteer.dart @@ -0,0 +1,72 @@ +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; +import 'package:talawa/models/user/user_info.dart'; + +/// This class creates an event volunteer model and returns an EventVolunteer instance. +class EventVolunteer { + EventVolunteer({ + this.id, + this.creator, + this.event, + this.group, + this.isAssigned, + this.isInvited, + this.response, + this.user, + }); + + // Creating a new EventVolunteer instance from a map structure. + factory EventVolunteer.fromJson(Map json) { + return EventVolunteer( + id: json['_id'] as String?, + creator: json['creator'] != null + ? User.fromJson( + json['creator'] as Map, + fromOrg: true, + ) + : null, + event: json['event'] != null + ? Event.fromJson(json['event'] as Map) + : null, + group: json['group'] != null + ? EventVolunteerGroup.fromJson(json['group'] as Map) + : null, + isAssigned: json['isAssigned'] as bool?, + isInvited: json['isInvited'] as bool?, + response: json['response'] as String?, + user: json['user'] != null + ? User.fromJson(json['user'] as Map, fromOrg: true) + : null, + ); + } + + /// Unique identifier for the event volunteer. + String? id; + + /// The creation date of the event volunteer. + String? createdAt; + + /// The creator of the event volunteer. + User? creator; + + /// The event associated with the event volunteer. + Event? event; + + /// The group associated with the event volunteer. + EventVolunteerGroup? group; + + /// A boolean value that indicates if the volunteer is assigned. + bool? isAssigned; + + /// A boolean value that indicates if the volunteer is invited. + bool? isInvited; + + /// The response of the volunteer. + String? response; + + /// The last update date of the event volunteer. + String? updatedAt; + + /// The user who is the volunteer. + User? user; +} diff --git a/lib/models/events/event_volunteer_group.dart b/lib/models/events/event_volunteer_group.dart new file mode 100644 index 000000000..b2edda023 --- /dev/null +++ b/lib/models/events/event_volunteer_group.dart @@ -0,0 +1,74 @@ +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer.dart'; +import 'package:talawa/models/user/user_info.dart'; + +/// This class creates an event volunteer group model and returns an EventVolunteerGroup instance. +class EventVolunteerGroup { + EventVolunteerGroup({ + this.id, + this.createdAt, + this.creator, + this.event, + this.leader, + this.name, + this.updatedAt, + this.volunteers, + this.volunteersRequired, + }); + + // Creating a new EventVolunteerGroup instance from a map structure. + factory EventVolunteerGroup.fromJson(Map json) { + return EventVolunteerGroup( + id: json['_id'] as String?, + createdAt: json['createdAt'] as String?, + creator: json['creator'] == null + ? null + : User.fromJson( + json['creator'] as Map, + fromOrg: true, + ), + event: json['event'] == null + ? null + : Event.fromJson(json['event'] as Map), + leader: json['leader'] == null + ? null + : User.fromJson( + json['leader'] as Map, + fromOrg: true, + ), + name: json['name'] as String?, + updatedAt: json['updatedAt'] as String?, + volunteers: (json['volunteers'] as List?) + ?.map((e) => EventVolunteer.fromJson(e as Map)) + .toList(), + volunteersRequired: json['volunteersRequired'] as int?, + ); + } + + /// Unique identifier for the event volunteer group. + String? id; + + /// The creation date of the event volunteer group. + String? createdAt; + + /// The creator of the event volunteer group. + User? creator; + + /// The event associated with the event volunteer group. + Event? event; + + /// The leader of the event volunteer group. + User? leader; + + /// The name of the event volunteer group. + String? name; + + /// The last update date of the event volunteer group. + String? updatedAt; + + /// The list of volunteers in the event volunteer group. + List? volunteers; + + /// The number of volunteers required for the event volunteer group. + int? volunteersRequired; +} diff --git a/lib/router.dart b/lib/router.dart index fe1dd2347..99258f45d 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -3,12 +3,14 @@ import 'package:flutter/material.dart'; import 'package:talawa/constants/routing_constants.dart'; import 'package:talawa/main.dart'; import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; import 'package:talawa/models/mainscreen_navigation_args.dart'; import 'package:talawa/models/organization/org_info.dart'; import 'package:talawa/models/post/post_model.dart'; import 'package:talawa/splash_screen.dart'; import 'package:talawa/view_model/after_auth_view_models/chat_view_models/direct_chat_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/create_event_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart'; import 'package:talawa/views/after_auth_screens/add_post_page.dart'; import 'package:talawa/views/after_auth_screens/app_settings/app_settings_page.dart'; import 'package:talawa/views/after_auth_screens/chat/chat_message_screen.dart'; @@ -19,6 +21,8 @@ import 'package:talawa/views/after_auth_screens/events/edit_event_page.dart'; import 'package:talawa/views/after_auth_screens/events/event_calendar.dart'; import 'package:talawa/views/after_auth_screens/events/event_info_page.dart'; import 'package:talawa/views/after_auth_screens/events/explore_events.dart'; +import 'package:talawa/views/after_auth_screens/events/manage_volunteer_group.dart'; +import 'package:talawa/views/after_auth_screens/events/volunteer_groups_screen.dart'; import 'package:talawa/views/after_auth_screens/feed/individual_post.dart'; import 'package:talawa/views/after_auth_screens/feed/organization_feed.dart'; import 'package:talawa/views/after_auth_screens/feed/pinned_post_page.dart'; @@ -310,6 +314,22 @@ Route generateRoute(RouteSettings settings) { ), ); + case Routes.volunteerGroupScreen: + final List arguments = settings.arguments! as List; + final Event event = arguments[0] as Event; + final EventInfoViewModel model = arguments[1] as EventInfoViewModel; + return MaterialPageRoute( + builder: (context) => VolunteerGroupsScreen(event: event, model: model), + ); + + case Routes.manageVolunteerGroup: + final List arguments = settings.arguments! as List; + final Event event = arguments[0] as Event; + final EventVolunteerGroup group = arguments[1] as EventVolunteerGroup; + return MaterialPageRoute( + builder: (context) => ManageGroupScreen(group: group, event: event), + ); + default: return MaterialPageRoute( builder: (context) => const DemoPageView( diff --git a/lib/services/database_mutation_functions.dart b/lib/services/database_mutation_functions.dart index c40eaa9f5..48c1ea5a7 100644 --- a/lib/services/database_mutation_functions.dart +++ b/lib/services/database_mutation_functions.dart @@ -79,6 +79,7 @@ class DataBaseMutationFunctions { final QueryOptions options = QueryOptions( document: gql(query), variables: variables ?? {}, + fetchPolicy: FetchPolicy.networkOnly, ); final response = await cacheService.executeOrCacheOperation( operation: query, diff --git a/lib/services/event_service.dart b/lib/services/event_service.dart index f8d01461e..32b513b02 100644 --- a/lib/services/event_service.dart +++ b/lib/services/event_service.dart @@ -3,6 +3,7 @@ import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:talawa/constants/constants.dart'; import 'package:talawa/locator.dart'; import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; import 'package:talawa/models/organization/org_info.dart'; import 'package:talawa/services/caching/base_feed_manager.dart'; import 'package:talawa/services/database_mutation_functions.dart'; @@ -18,6 +19,9 @@ import 'package:talawa/utils/event_queries.dart'; /// * `registerForAnEvent` : to register for an event. /// * `deleteEvent` : to delete an event. /// * `editEvent` : to edit the event. +/// * `fetchEventVolunteers` : to fetch all volunteers of an event. +/// * `createVolunteerGroup` : to create a volunteer group. +/// * `addVolunteerToGroup` : to add a volunteer to a group. /// * `dispose` : to cancel the stream subscription of an organization. class EventService extends BaseFeedManager { EventService() : super(HiveKeys.eventFeedKey) { @@ -212,6 +216,115 @@ class EventService extends BaseFeedManager { return result; } + /// This function is used to create a volunteer group. + /// + /// **params**: + /// * `variables`: this will be `map` type and contain all the volunteer group details need to be created. + /// + /// **returns**: + /// * `Future`: Information about the created volunteer group. + Future createVolunteerGroup(Map variables) async { + final result = await _dbFunctions.gqlAuthMutation( + EventQueries().createVolunteerGroup(), + variables: {'data': variables}, + ); + return result; + } + + /// This function is used to remove a volunteer group. + /// + /// **params**: + /// * `variables`: This will be a `map` type and contain the ID of the volunteer group to be deleted. + /// + /// **returns**: + /// * `Future`: Information about the removed volunteer group. + Future removeVolunteerGroup(Map variables) async { + final result = await _dbFunctions.gqlAuthMutation( + EventQueries().removeEventVolunteerGroup(), + variables: variables, + ); + return result; + } + + /// This function is used to add a volunteer to a group. + /// + /// **params**: + /// * `variables`: this will be `map` type and contain all the details needed to add a volunteer to a group. + /// + /// **returns**: + /// * `Future`: Information about the added volunteer. + Future addVolunteerToGroup(Map variables) async { + final result = await _dbFunctions.gqlAuthMutation( + EventQueries().addVolunteerToGroup(), + variables: {'data': variables}, + ); + return result; + } + + /// This function is used to remove a volunteer from a group. + /// + /// **params**: + /// * `variables`: this will be `map` type and contain the ID of the volunteer to be removed. + /// + /// **returns**: + /// * `Future`: Information about the removed volunteer. + Future removeVolunteerFromGroup( + Map variables, + ) async { + final result = await _dbFunctions.gqlAuthMutation( + EventQueries().removeVolunteerMutation(), + variables: variables, + ); + return result; + } + + /// This function is used to update the information of a volunteer group. + /// + /// **params**: + /// * `variables`: This is a `Map` type that contains the ID of the volunteer group to be updated and the fields to be updated. + /// + /// **returns**: + /// * `Future`: Information about the updated volunteer group. + Future updateVolunteerGroup(Map variables) async { + final result = await _dbFunctions.gqlAuthMutation( + EventQueries().updateVolunteerGroupMutation(), + variables: variables, + ); + return result; + } + + /// This function is used to fetch all volunteer groups for an event. + /// + /// **params**: + /// * `eventId`: Id of the event to fetch volunteer groups. + /// + /// **returns**: + /// * `Future>`: returns the list of volunteer groups + Future> fetchVolunteerGroupsByEvent( + String eventId, + ) async { + try { + final variables = { + "where": {"eventId": eventId}, + }; + final result = await _dbFunctions.gqlAuthQuery( + EventQueries().fetchVolunteerGroups(), + variables: variables, + ); + final List groupsJson = result.data!['getEventVolunteerGroups'] as List; + + return groupsJson + .map( + (groupJson) => + EventVolunteerGroup.fromJson(groupJson as Map), + ) + .toList(); + } catch (e) { + print('Error fetching volunteer groups: $e'); + rethrow; + } + } + /// This function is used to cancel the stream subscription of an organization. /// /// **params**: diff --git a/lib/utils/event_queries.dart b/lib/utils/event_queries.dart index 554c180f4..b4abcd8ed 100644 --- a/lib/utils/event_queries.dart +++ b/lib/utils/event_queries.dart @@ -190,4 +190,153 @@ class EventQueries { } }"""; } + + /// Creates a GraphQL mutation for creating an event volunteer group. + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `String`: Returns a GraphQL mutation string to create an event volunteer group. + /// + /// This function generates a GraphQL mutation string for creating an event volunteer group. + String createVolunteerGroup() { + return ''' + mutation CreateEventVolunteerGroup(\$data: EventVolunteerGroupInput!) { + createEventVolunteerGroup(data: \$data) { + _id + name + volunteers{ + _id + } + createdAt + volunteersRequired + creator{ + _id + } + } + } + '''; + } + + /// Creates a GraphQL mutation for removing an event volunteer group. + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `String`: Returns a GraphQL mutation string to remove an event volunteer group. + /// + /// This function generates a GraphQL mutation string for removing an event volunteer group. + String removeEventVolunteerGroup() { + return ''' + mutation RemoveEventVolunteerGroup(\$id: ID!) { + removeEventVolunteerGroup(id: \$id) { + _id + name + } + } + '''; + } + + /// Creates a GraphQL mutation for adding a volunteer to a group. + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `String`: Returns a GraphQL mutation string to add a volunteer to a group. + /// + /// This function generates a GraphQL mutation string for adding a volunteer to a group. + String addVolunteerToGroup() { + return ''' + mutation CreateEventVolunteer(\$data: EventVolunteerInput!) { + createEventVolunteer(data: \$data) { + _id + isAssigned + response + creator{ + _id + } + group{ + _id + name + } + isInvited + user{ + _id + firstName + lastName + } + } + } + '''; + } + + /// Creates a GraphQL mutation for deleting a volunteer from a group. + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `String`: Returns a GraphQL mutation string to delete a volunteer to a group. + /// + /// This function generates a GraphQL mutation string for deleting a volunteer to a group. + String removeVolunteerMutation() { + return ''' + mutation RemoveEventVolunteer(\$id: ID!) { + removeEventVolunteer(id: \$id) { + _id + } + } + '''; + } + + /// Mutation to update volunteer group insatnce. + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `String`: Returns a GraphQL mutation string to update a volunteer group + String updateVolunteerGroupMutation() { + return ''' + mutation UpdateEventVolunteerGroup(\$id: ID!, \$data: UpdateEventVolunteerGroupInput!) { + updateEventVolunteerGroup(id: \$id, data: \$data) { + _id + name + volunteersRequired + } + } + '''; + } + + /// Fetches event volunteer groups based on criteria such as event ID. + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `String`: Returns a GraphQL query string to fetch event volunteer groups that match the provided criteria. + String fetchVolunteerGroups() { + return ''' + query GetEventVolunteerGroups(\$where: EventVolunteerGroupWhereInput) { + getEventVolunteerGroups(where: \$where) { + _id + name + volunteersRequired + createdAt + volunteers{ + _id + response + user{ + _id + firstName + lastName + } + } + } + } + '''; + } } diff --git a/lib/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart b/lib/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart index 8835a662e..7072fd132 100644 --- a/lib/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart +++ b/lib/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:talawa/enums/enums.dart'; import 'package:talawa/locator.dart'; import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; import 'package:talawa/services/event_service.dart'; import 'package:talawa/services/user_config.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; @@ -22,6 +24,11 @@ class EventInfoViewModel extends BaseModel { /// List of Attendee type to store the attendees data. late List attendees = []; + late final List _volunteerGroups = []; + + /// List of volunteer groups of an event. + List get volunteerGroups => _volunteerGroups; + /// This function initializes the EventInfoViewModel class with the required arguments. /// /// **params**: @@ -34,10 +41,7 @@ class EventInfoViewModel extends BaseModel { exploreEventsInstance = args["exploreEventViewModel"] as ExploreEventsViewModel; fabTitle = getFabTitle(); - setState(ViewState.busy); - attendees = event.attendees ?? []; - setState(ViewState.idle); } /// The function allows user to register for an event. @@ -94,4 +98,68 @@ class EventInfoViewModel extends BaseModel { return "Register"; } } + + /// This function is used to create a new volunteer group for an event. + /// + /// **params**: + /// * `event`: Name of the group + /// * `groupName`: Name of the group + /// * `volunteersRequired`: Total number of volunteers required for the group + /// + /// **returns**: + /// * `Future`: returns the new volunteer group for an event + Future createVolunteerGroup( + Event event, + String groupName, + int volunteersRequired, + ) async { + try { + final variables = { + 'eventId': event.id, + 'name': groupName, + 'volunteersRequired': volunteersRequired, + }; + + final result = await locator() + .createVolunteerGroup(variables) as QueryResult; + + if (result.data == null || + result.data!['createEventVolunteerGroup'] == null) { + throw Exception('Failed to create volunteer group or no data returned'); + } + + final data = result.data!['createEventVolunteerGroup']; + final newGroup = + EventVolunteerGroup.fromJson(data as Map); + + _volunteerGroups.add(newGroup); + notifyListeners(); + + return newGroup; + } catch (e) { + print('Error creating volunteer group: $e'); + } + return null; + } + + /// Fetches all volunteer groups for the current event. + /// + /// **params**: + /// * `eventId`: The ID of the event to fetch volunteer groups for. + /// + /// **returns**: + /// None + Future fetchVolunteerGroups(String eventId) async { + try { + final result = + await locator().fetchVolunteerGroupsByEvent(eventId); + + _volunteerGroups.clear(); + _volunteerGroups.addAll(result); + notifyListeners(); + } catch (e) { + print('Error fetching volunteer groups: $e'); + setState(ViewState.idle); + } + } } diff --git a/lib/view_model/after_auth_view_models/event_view_models/manage_volunteer_group_view_model.dart b/lib/view_model/after_auth_view_models/event_view_models/manage_volunteer_group_view_model.dart new file mode 100644 index 000000000..bf0006ae1 --- /dev/null +++ b/lib/view_model/after_auth_view_models/event_view_models/manage_volunteer_group_view_model.dart @@ -0,0 +1,205 @@ +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/services/event_service.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +/// A ViewModel for managing volunteer groups within an event. +/// +/// This class handles operations related to volunteer groups including +/// initializing the group, adding/removing volunteers, and updating group details. +class ManageVolunteerGroupViewModel extends BaseModel { + /// The event associated with the volunteer group. + late Event event; + + /// List of organization members. + late List orgMembersList = []; + + /// A map to track the selection state of organization members. + late final Map _memberCheckedMap = {}; + + /// Gets the map of member IDs and their selection state. + Map get memberCheckedMap => _memberCheckedMap; + + /// List of volunteers in the group. + List _volunteers = []; + + /// Gets the list of volunteers in the group. + List get volunteers => _volunteers; + + /// Indicates whether the view model is currently fetching volunteers. + final bool _isFetchingVolunteers = false; + + /// Gets whether the view model is currently fetching volunteers. + bool get isFetchingVolunteers => _isFetchingVolunteers; + + /// Initializes the view model with the given event and volunteer group. + /// + /// **params**: + /// * `parentEvent`: The event associated with the volunteer group. + /// * `group`: The volunteer group to be managed. + /// + /// **returns**: + /// None + Future initialize(Event parentEvent, EventVolunteerGroup group) async { + setState(ViewState.busy); + + event = parentEvent; + if (group.volunteers != null) { + _volunteers = List.from(group.volunteers!); + } + + setState(ViewState.idle); + } + + /// Fetches the list of current organization members. + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `Future>`: A list of organization members. + Future> getCurrentOrgUsersList() async { + if (orgMembersList.isEmpty) { + orgMembersList = await organizationService + .getOrgMembersList(userConfig.currentOrg.id!); + } + final availableMembers = orgMembersList.where((member) { + return !volunteers.any((volunteer) => volunteer.user!.id == member.id); + }).toList(); + + for (final member in availableMembers) { + _memberCheckedMap.putIfAbsent(member.id!, () => false); + } + + return availableMembers; + } + + /// Adds a volunteer to the specified group. + /// + /// **params**: + /// * `volunteerId`: The ID of the volunteer to add. + /// * `eventId`: The ID of the event. + /// * `groupId`: The ID of the group. + /// + /// **returns**: + /// None + Future addVolunteerToGroup( + String volunteerId, + String eventId, + String groupId, + ) async { + try { + final variables = { + 'eventId': eventId, + 'userId': volunteerId, + 'groupId': groupId, + }; + final result = await locator() + .addVolunteerToGroup(variables) as QueryResult; + final data = result.data!; + final addedVolunteerData = + data['createEventVolunteer'] as Map; + final addedVolunteer = EventVolunteer.fromJson(addedVolunteerData); + _volunteers.add(addedVolunteer); + notifyListeners(); + } catch (e) { + print('Error adding volunteer to group: $e'); + } + } + + /// Deletes a volunteer group. + /// + /// **params**: + /// * `groupId`: The ID of the group to delete. + /// + /// **returns**: + /// None + Future deleteVolunteerGroup(String groupId) async { + try { + final variables = { + 'id': groupId, + }; + final result = await locator() + .removeVolunteerGroup(variables) as QueryResult; + final data = result.data; + + if (data != null && data['removeEventVolunteerGroup'] != null) { + notifyListeners(); + } + } catch (e) { + print('Error deleting volunteer group: $e'); + } + } + + /// Removes a volunteer from the group. + /// + /// **params**: + /// * `volunteerId`: The ID of the volunteer to remove. + /// + /// **returns**: + /// None + Future removeVolunteerFromGroup(String volunteerId) async { + try { + final variables = { + 'id': volunteerId, + }; + final result = await locator() + .removeVolunteerFromGroup(variables) as QueryResult; + final data = result.data; + + if (data != null && data['removeEventVolunteer'] != null) { + _volunteers.removeWhere((volunteer) => volunteer.id == volunteerId); + print('Volunteer removed successfully.'); + notifyListeners(); + } else { + print('Failed to remove volunteer.'); + } + } catch (e) { + print('Error removing volunteer: $e'); + } + } + + /// Updates the details of a volunteer group. + /// + /// **params**: + /// * `group`: The volunteer group to update. + /// * `eventId`: The ID of the event. + /// * `name`: The new name for the group. + /// * `volunteersRequired`: The new number of volunteers required. + /// + /// **returns**: + /// None + Future updateVolunteerGroup( + EventVolunteerGroup group, + String eventId, + String name, + int volunteersRequired, + ) async { + final variables = { + 'id': group.id, + 'data': { + 'eventId': eventId, + 'name': name, + 'volunteersRequired': volunteersRequired, + }, + }; + + try { + final result = await locator() + .updateVolunteerGroup(variables) as QueryResult; + + if (result.data != null) { + group.name = name; + group.volunteersRequired = volunteersRequired; + notifyListeners(); + } + } catch (e) { + print('Error updating volunteer group: $e'); + } + } +} diff --git a/lib/views/after_auth_screens/events/event_info_page.dart b/lib/views/after_auth_screens/events/event_info_page.dart index 3e96d7b55..c76962d99 100644 --- a/lib/views/after_auth_screens/events/event_info_page.dart +++ b/lib/views/after_auth_screens/events/event_info_page.dart @@ -5,6 +5,7 @@ import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; import 'package:talawa/views/after_auth_screens/events/event_info_body.dart'; +import 'package:talawa/views/after_auth_screens/events/volunteer_groups_screen.dart'; import 'package:talawa/views/base_view.dart'; /// EventInfoPage returns a widget that has mutable state _EventInfoPageState. @@ -13,74 +14,103 @@ class EventInfoPage extends StatefulWidget { /// Takes in Arguments for the Page. final Map args; + @override _EventInfoPageState createState() => _EventInfoPageState(); } -/// _EventInfoPageState returns a widget of a Page for particular event. -class _EventInfoPageState extends State { +class _EventInfoPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + bool _showFloatingActionButton = true; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + _tabController.addListener(() { + setState(() { + _showFloatingActionButton = _tabController.index == 0; + }); + }); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return BaseView( onModelReady: (model) => model.initialize(args: widget.args), builder: (context, model, child) { return Scaffold( - body: CustomScrollView( - slivers: [ - // SliverAppBar is a Material Design app bar that integrates with a CustomScrollView. - SliverAppBar( - // Translated title of the App bar. - title: Text( - AppLocalizations.of(context)! - .strictTranslate('Event Details'), - ), - // actions: [ - // IconButton( - // // Button to share the event in the social medias. - // icon: const Icon(Icons.share), - // onPressed: () => SocialShare.shareOptions( - // 'https://cyberwake.github.io/applink/eventInvite?setUrl=${GraphqlConfig.orgURI}&selectOrg=${userConfig.currentOrg.id!}&eventId=${model.event.id}', - // ), - // ), - // ], - pinned: true, - expandedHeight: SizeConfig.screenWidth, - flexibleSpace: FlexibleSpaceBar( - background: Image.network( - 'https://picsum.photos/id/26/200/300', - fit: BoxFit.fill, - ), + appBar: AppBar( + title: Text( + AppLocalizations.of(context)!.strictTranslate('Event Details'), + ), + bottom: TabBar( + key: const Key("tabBar"), + controller: _tabController, + tabs: const [ + Tab(text: "Info"), + Tab(text: "Volunteers"), + ], + ), + ), + body: TabBarView( + controller: _tabController, + children: [ + Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + automaticallyImplyLeading: false, + pinned: true, + expandedHeight: SizeConfig.screenWidth, + flexibleSpace: FlexibleSpaceBar( + background: Image.network( + 'https://picsum.photos/id/26/200/300', + fit: BoxFit.fill, + ), + ), + ), + const EventInfoBody(), + ], ), + floatingActionButton: _showFloatingActionButton + ? (model.event.creator != null && + model.event.creator!.id != + userConfig.currentUser.id) + ? FloatingActionButton.extended( + key: const Key("registerEventFloatingbtn"), + onPressed: () { + model.registerForEvent(); + }, + label: Text( + AppLocalizations.of(context)! + .strictTranslate(model.fabTitle), + style: Theme.of(context).textTheme.bodyMedium, + ), + ) + : FloatingActionButton( + onPressed: () { + (widget.args["exploreEventViewModel"] + as ExploreEventsViewModel) + .deleteEvent(eventId: model.event.id!); + }, + foregroundColor: + Theme.of(context).colorScheme.secondary, + backgroundColor: Theme.of(context).primaryColor, + child: const Icon(Icons.delete), + ) + : null, ), - const EventInfoBody(), + VolunteerGroupsScreen(event: model.event, model: model), ], ), - // if the event is created by current user then renders explore - // button in the event page else renders register button. - floatingActionButton: - model.event.creator!.id != userConfig.currentUser.id - ? FloatingActionButton.extended( - onPressed: () { - model.registerForEvent(); - }, - label: Text( - AppLocalizations.of(context)! - .strictTranslate(model.fabTitle), - style: Theme.of(context).textTheme.bodyMedium, - ), - ) - : FloatingActionButton( - onPressed: () { - (widget.args["exploreEventViewModel"] - as ExploreEventsViewModel) - .deleteEvent(eventId: model.event.id!); - }, - foregroundColor: Theme.of(context).colorScheme.secondary, - backgroundColor: Theme.of(context).primaryColor, - child: const Icon( - Icons.delete, - ), - ), ); }, ); diff --git a/lib/views/after_auth_screens/events/manage_volunteer_group.dart b/lib/views/after_auth_screens/events/manage_volunteer_group.dart new file mode 100644 index 000000000..9ee7eb2ce --- /dev/null +++ b/lib/views/after_auth_screens/events/manage_volunteer_group.dart @@ -0,0 +1,517 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/manage_volunteer_group_view_model.dart'; +import 'package:talawa/views/base_view.dart'; + +/// ManageGroupScreen handles the display and management of volunteers within a specific group. +/// +/// This screen shows the list of volunteers, their current status (Accepted, Rejected, Pending), +/// and allows the user to add new volunteers, edit group details, or delete the group. +class ManageGroupScreen extends StatelessWidget { + const ManageGroupScreen({ + super.key, + required this.group, + required this.event, + }); + + /// The volunteer group being managed. + final EventVolunteerGroup group; + + /// The event to which the volunteer group belongs. + final Event event; + + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialize(event, group), + builder: (context, model, child) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + backgroundColor: Theme.of(context).primaryColor, + title: Text( + group.name!, + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.w600, + fontSize: 20, + color: Colors.white, + ), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (model.volunteers.isNotEmpty) + Container( + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric( + vertical: 12.0, + horizontal: 16.0, + ), + margin: const EdgeInsets.only(bottom: 10.0), + child: const Row( + children: [ + Expanded( + flex: 3, + child: Text( + 'Volunteer', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + fontSize: 16, + ), + ), + ), + Expanded( + flex: 6, + child: Text( + 'Status', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + fontSize: 16, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + Expanded( + child: model.volunteers.isEmpty + ? Center( + child: Text( + 'No volunteers yet', + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith( + fontWeight: FontWeight.w600, + fontSize: 20, + color: Colors.white, + ), + ), + ) + : ListView.builder( + itemCount: model.volunteers.length, + itemBuilder: (context, index) { + final volunteer = model.volunteers[index]; + final response = volunteer.response; + + String status; + Color statusColor; + + switch (response) { + case 'YES': + status = 'Accepted'; + statusColor = Colors.green; + break; + case 'NO': + status = 'Rejected'; + statusColor = Colors.red; + break; + default: + status = 'Pending'; + statusColor = Colors.grey; + } + + return ListTile( + key: const Key("volunteers"), + title: Text( + '${volunteer.user!.firstName} ${volunteer.user!.lastName}', + style: const TextStyle( + color: Colors.blue, + fontSize: 16, + ), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(right: 24), + child: Text( + status, + style: TextStyle( + color: statusColor, + fontSize: 16, + ), + ), + ), + IconButton( + key: Key("delete_volunteer$index"), + icon: const Icon( + Icons.delete, + color: Colors.red, + ), + onPressed: () { + model.removeVolunteerFromGroup( + volunteer.id!, + ); + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text('Volunteer removed'), + duration: Duration(seconds: 1), + ), + ); + }, + ), + ], + ), + ); + }, + ), + ), + if (model.volunteers.isNotEmpty) + _buildVolunteerStatusSummary(model), + SizedBox(height: SizeConfig.screenHeight! * 0.011), + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + _showAddVolunteerBottomSheet(context, model); + }, + icon: const Icon( + Icons.add, + color: Colors.white, + ), + label: const Text( + 'Add Volunteers', + style: TextStyle(color: Colors.white, fontSize: 16), + ), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + ), + ), + ), + ], + ), + SizedBox(height: SizeConfig.screenHeight! * 0.01), + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + _showEditGroupDialog(context, model); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + ), + label: const Text( + 'Edit Group', + style: TextStyle(color: Colors.white, fontSize: 15), + ), + icon: const Icon( + Icons.edit, + color: Colors.white, + ), + ), + ), + SizedBox(width: SizeConfig.screenWidth! * 0.02), + Expanded( + child: ElevatedButton.icon( + onPressed: () async { + final bool? confirm = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + key: const Key("Delete_group_dialogue"), + title: const Text('Confirm Deletion'), + content: const Text( + 'Are you sure you want to delete this group?', + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: const Text('Delete'), + ), + ], + ); + }, + ); + if (confirm == true) { + await model.deleteVolunteerGroup(group.id!); + if (context.mounted) { + Navigator.pop(context, true); + } + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + ), + label: const Text( + 'Delete Group', + style: TextStyle(color: Colors.white, fontSize: 15), + ), + icon: const Icon( + Icons.delete, + color: Colors.white, + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } + + /// Builds a summary of the volunteer statuses for the group.. + /// + /// **params**: + /// * `model`: The view model managing the group + /// + /// **returns**: + /// * `Widget`: A [Widget] displaying the volunteer status summary + Widget _buildVolunteerStatusSummary(ManageVolunteerGroupViewModel model) { + final int accepted = model.volunteers + .where((volunteer) => volunteer.response == 'YES') + .length; + final int pending = model.volunteers + .where((volunteer) => volunteer.response == null) + .length; + final int needed = group.volunteersRequired! - accepted; + + return Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + color: Colors.blueGrey, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Accepted Volunteers: $accepted', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + Text( + 'Pending Volunteers: $pending', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + Text( + 'Volunteers Still Needed: ${needed > 0 ? needed : 0}', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ], + ), + ); + } + + /// Shows a dialog allowing the user to edit the group's name and required volunteers. + /// + /// **params**: + /// * `context`: [BuildContext] - The build context. + /// * `model`: [ManageVolunteerGroupViewModel] - The view model managing the group. + /// + /// **returns**: + /// None + void _showEditGroupDialog( + BuildContext context, + ManageVolunteerGroupViewModel model, + ) { + final TextEditingController nameController = + TextEditingController(text: group.name); + final TextEditingController volunteersRequiredController = + TextEditingController(text: group.volunteersRequired.toString()); + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Edit Group'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + key: const Key('groupNameField'), + controller: nameController, + decoration: const InputDecoration( + labelText: 'Group Name', + ), + ), + TextField( + key: const Key('groupVolunteerRequiredField'), + controller: volunteersRequiredController, + decoration: const InputDecoration( + labelText: 'Volunteers Required', + ), + keyboardType: TextInputType.number, + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () async { + final String newName = nameController.text.trim(); + final int newVolunteersRequired = + int.tryParse(volunteersRequiredController.text.trim())!; + + if (newName.isNotEmpty && newVolunteersRequired > 0) { + await model.updateVolunteerGroup( + group, + event.id!, + newName, + newVolunteersRequired, + ); + + if (context.mounted) { + Navigator.of(context).pop(); + } + } + }, + child: const Text('Save'), + ), + ], + ); + }, + ); + } + + /// Shows the bottom sheet for adding a new volunteer to the group. + /// + /// **params**: + /// * `context`: [BuildContext] - The build context. + /// * `model`: [ManageVolunteerGroupViewModel] - The view model managing the group. + /// + /// **returns**: + /// None + void _showAddVolunteerBottomSheet( + BuildContext context, + ManageVolunteerGroupViewModel model, + ) { + model.getCurrentOrgUsersList().then((members) { + if (context.mounted) { + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + ), + ), + isScrollControlled: true, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + ), + child: Container( + key: const Key("bottomSheetContainer"), + height: MediaQuery.of(context).size.height * 0.8, + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + ), + child: Padding( + padding: const EdgeInsets.all(10), + child: Column( + children: [ + const Text( + 'Add Volunteers', + style: TextStyle(fontSize: 16), + ), + TextButton( + onPressed: () async { + for (final member + in model.memberCheckedMap.entries) { + if (member.value) { + await model.addVolunteerToGroup( + member.key, + event.id!, + group.id!, + ); + } + } + if (context.mounted) { + model.memberCheckedMap.clear(); + Navigator.pop(context); + } + }, + child: const Text('Done'), + ), + const Divider(), + members.isEmpty + ? const Center( + child: Text( + "There aren't any members in this organization.", + ), + ) + : Flexible( + child: ListView.builder( + key: const Key("members_list_key"), + shrinkWrap: true, + itemCount: members.length, + itemBuilder: (context, index) { + return CheckboxListTile( + key: Key("checkBox$index"), + checkColor: Theme.of(context) + .colorScheme + .surface, + activeColor: Theme.of(context) + .colorScheme + .primary, + title: Text( + "${members[index].firstName!} ${members[index].lastName!}", + ), + value: model.memberCheckedMap[ + members[index].id], + onChanged: (val) { + setState(() { + model.memberCheckedMap[ + members[index].id!] = val!; + }); + }, + ); + }, + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, + ); + } + }); + } +} diff --git a/lib/views/after_auth_screens/events/volunteer_groups_screen.dart b/lib/views/after_auth_screens/events/volunteer_groups_screen.dart new file mode 100644 index 000000000..2d8dbf8a1 --- /dev/null +++ b/lib/views/after_auth_screens/events/volunteer_groups_screen.dart @@ -0,0 +1,300 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart'; + +/// A screen that displays the volunteer groups for a specific event. +class VolunteerGroupsScreen extends StatefulWidget { + const VolunteerGroupsScreen({ + super.key, + required this.event, + required this.model, + }); + + /// The event for which volunteer groups are displayed. + final Event event; + + /// The view model that manages the event information. + final EventInfoViewModel model; + + @override + State createState() => _VolunteerGroupsScreenState(); +} + +class _VolunteerGroupsScreenState extends State { + /// Formats a date string into 'yyyy-MM-dd' format. + /// + /// **params**: + /// * `dateStr`:The date string to format + /// + /// **returns**: + /// * `String`:A formatted date string + String _formatDate(String? dateStr) { + if (dateStr == null) return 'N/A'; + + try { + final DateTime dateTime = DateTime.parse(dateStr); + final DateFormat formatter = DateFormat('yyyy-MM-dd'); + return formatter.format(dateTime); + } catch (e) { + return 'Invalid date'; + } + } + + @override + void initState() { + super.initState(); + _fetchVolunteerGroupsAndDisplay(); + } + + /// method to fetch all volunteer groups. + /// + /// **params**: + /// None + /// + /// **returns**: + /// None + Future _fetchVolunteerGroupsAndDisplay() async { + await widget.model.fetchVolunteerGroups(widget.event.id!); + // Ensure setState is called after the widget is built + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() {}); + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton( + key: const Key("add_group_btn"), + backgroundColor: Colors.green, + child: const Icon( + Icons.add, + color: Colors.white, + size: 30, + ), + onPressed: () { + _showCreateGroupDialog(context, widget.model); + }, + ), + body: RefreshIndicator( + onRefresh: () async { + widget.model.fetchVolunteerGroups(widget.event.id!); + }, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 20), + if (widget.model.volunteerGroups.isNotEmpty) + Container( + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric( + vertical: 12.0, + horizontal: 10.0, + ), + margin: const EdgeInsets.only(bottom: 10.0), + child: const Row( + children: [ + Expanded( + flex: 3, + child: Text( + 'Group Name', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + fontSize: 14, + ), + ), + ), + Expanded( + flex: 3, + child: Text( + 'Created At', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ), + Expanded( + flex: 3, + child: Text( + 'Manage', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + fontSize: 15, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + Expanded( + child: widget.model.volunteerGroups.isEmpty + ? const Center( + child: Text( + "There aren't any volunteer groups", + style: TextStyle(color: Colors.white, fontSize: 18), + ), + ) + : ListView.builder( + itemCount: widget.model.volunteerGroups.length, + itemBuilder: (context, index) { + final group = widget.model.volunteerGroups[index]; + return Row( + key: const Key("group_data"), + children: [ + Expanded( + flex: 3, + child: Container( + padding: + const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + group.name!, + style: const TextStyle( + color: Colors.blue, + fontSize: 16, + ), + textAlign: TextAlign.center, + ), + ), + ), + Expanded( + flex: 3, + child: Container( + padding: + const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + _formatDate(group.createdAt), + style: const TextStyle(color: Colors.white), + textAlign: TextAlign.center, + ), + ), + ), + Expanded( + flex: 3, + child: IconButton( + icon: const Icon( + Icons.edit, + color: Colors.green, + ), + onPressed: () async { + navigationService.pushScreen( + "/manageVolunteerScreen", + arguments: [ + widget.event, + widget.model.volunteerGroups[index], + ], + ); + }, + ), + ), + ], + ); + }, + ), + ), + ], + ), + ), + ), + ); + } + + /// Displays a dialog for creating a new volunteer group. + /// + /// **params**: + /// * `context`: The build context where the dialog should be displayed + /// * `model`: The view model that manages event-related operations + /// + /// **returns**: + /// None + void _showCreateGroupDialog(BuildContext context, EventInfoViewModel model) { + final groupNameController = TextEditingController(); + final volunteersRequiredController = TextEditingController(); + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + key: const Key("add_grp_dialogue"), + backgroundColor: const Color.fromARGB(255, 34, 34, 34), + title: const Text('Create Volunteer Group'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + key: const Key("group_name_field"), + controller: groupNameController, + decoration: const InputDecoration(labelText: 'Group Name'), + ), + TextField( + key: const Key("volunteers_required_field"), + controller: volunteersRequiredController, + keyboardType: TextInputType.number, + decoration: + const InputDecoration(labelText: 'Volunteers Required'), + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () async { + final groupName = groupNameController.text; + final volunteersRequired = + int.tryParse(volunteersRequiredController.text) ?? 0; + + if (groupName.isNotEmpty && volunteersRequired > 0) { + final newGroup = await model.createVolunteerGroup( + widget.event, + groupName, + volunteersRequired, + ); + + if (newGroup != null) { + if (context.mounted) { + Navigator.of(context).pop(); + navigationService.pushScreen( + "/manageVolunteerScreen", + arguments: [widget.event, newGroup], + ); + } + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Failed to create group')), + ); + } + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please enter valid data')), + ); + } + }, + child: const Text('Create Group'), + ), + ], + ); + }, + ); + } +} diff --git a/test/helpers/test_helpers.mocks.dart b/test/helpers/test_helpers.mocks.dart index 75b39b4f7..a0c12cc72 100644 --- a/test/helpers/test_helpers.mocks.dart +++ b/test/helpers/test_helpers.mocks.dart @@ -9,48 +9,49 @@ import 'dart:ui' as _i10; import 'package:flutter/material.dart' as _i1; import 'package:graphql_flutter/graphql_flutter.dart' as _i3; -import 'package:image_cropper/src/cropper.dart' as _i40; +import 'package:image_cropper/src/cropper.dart' as _i41; import 'package:image_cropper_platform_interface/image_cropper_platform_interface.dart' - as _i41; + as _i42; import 'package:image_picker/image_picker.dart' as _i13; import 'package:mockito/mockito.dart' as _i2; import 'package:mockito/src/dummies.dart' as _i18; -import 'package:qr_code_scanner/src/qr_code_scanner.dart' as _i32; -import 'package:qr_code_scanner/src/types/barcode.dart' as _i33; -import 'package:qr_code_scanner/src/types/camera.dart' as _i34; +import 'package:qr_code_scanner/src/qr_code_scanner.dart' as _i33; +import 'package:qr_code_scanner/src/types/barcode.dart' as _i34; +import 'package:qr_code_scanner/src/types/camera.dart' as _i35; import 'package:qr_code_scanner/src/types/features.dart' as _i12; import 'package:talawa/enums/enums.dart' as _i14; -import 'package:talawa/models/chats/chat_list_tile_data_model.dart' as _i23; -import 'package:talawa/models/chats/chat_message.dart' as _i24; +import 'package:talawa/models/chats/chat_list_tile_data_model.dart' as _i24; +import 'package:talawa/models/chats/chat_message.dart' as _i25; import 'package:talawa/models/events/event_model.dart' as _i21; -import 'package:talawa/models/events/event_venue.dart' as _i38; +import 'package:talawa/models/events/event_venue.dart' as _i39; +import 'package:talawa/models/events/event_volunteer_group.dart' as _i22; import 'package:talawa/models/organization/org_info.dart' as _i6; import 'package:talawa/models/post/post_model.dart' as _i17; import 'package:talawa/models/user/user_info.dart' as _i7; -import 'package:talawa/services/chat_service.dart' as _i22; -import 'package:talawa/services/comment_service.dart' as _i35; +import 'package:talawa/services/chat_service.dart' as _i23; +import 'package:talawa/services/comment_service.dart' as _i36; import 'package:talawa/services/database_mutation_functions.dart' as _i9; import 'package:talawa/services/event_service.dart' as _i11; import 'package:talawa/services/graphql_config.dart' as _i15; import 'package:talawa/services/navigation_service.dart' as _i8; -import 'package:talawa/services/org_service.dart' as _i28; +import 'package:talawa/services/org_service.dart' as _i29; import 'package:talawa/services/post_service.dart' as _i16; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart' as _i19; -import 'package:talawa/services/user_config.dart' as _i25; -import 'package:talawa/utils/validators.dart' as _i31; +import 'package:talawa/services/user_config.dart' as _i26; +import 'package:talawa/utils/validators.dart' as _i32; import 'package:talawa/view_model/after_auth_view_models/chat_view_models/direct_chat_view_model.dart' - as _i39; + as _i40; import 'package:talawa/view_model/after_auth_view_models/event_view_models/create_event_view_model.dart' - as _i37; + as _i38; import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart' - as _i29; -import 'package:talawa/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart' as _i30; -import 'package:talawa/view_model/lang_view_model.dart' as _i26; +import 'package:talawa/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart' + as _i31; +import 'package:talawa/view_model/lang_view_model.dart' as _i27; import 'package:talawa/view_model/pre_auth_view_models/signup_details_view_model.dart' - as _i27; -import 'package:talawa/view_model/theme_view_model.dart' as _i36; + as _i28; +import 'package:talawa/view_model/theme_view_model.dart' as _i37; import 'package:talawa/widgets/custom_alert_dialog.dart' as _i4; // ignore_for_file: type=lint @@ -1475,6 +1476,77 @@ class MockEventService extends _i2.Mock implements _i11.EventService { )), ) as _i5.Future<_i3.QueryResult>); + @override + _i5.Future createVolunteerGroup(Map? variables) => + (super.noSuchMethod( + Invocation.method( + #createVolunteerGroup, + [variables], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future removeVolunteerGroup(Map? variables) => + (super.noSuchMethod( + Invocation.method( + #removeVolunteerGroup, + [variables], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future addVolunteerToGroup(Map? variables) => + (super.noSuchMethod( + Invocation.method( + #addVolunteerToGroup, + [variables], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future removeVolunteerFromGroup( + Map? variables) => + (super.noSuchMethod( + Invocation.method( + #removeVolunteerFromGroup, + [variables], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future updateVolunteerGroup(Map? variables) => + (super.noSuchMethod( + Invocation.method( + #updateVolunteerGroup, + [variables], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future> fetchVolunteerGroupsByEvent( + String? eventId) => + (super.noSuchMethod( + Invocation.method( + #fetchVolunteerGroupsByEvent, + [eventId], + ), + returnValue: _i5.Future>.value( + <_i22.EventVolunteerGroup>[]), + returnValueForMissingStub: + _i5.Future>.value( + <_i22.EventVolunteerGroup>[]), + ) as _i5.Future>); + @override void dispose() => super.noSuchMethod( Invocation.method( @@ -1522,7 +1594,7 @@ class MockEventService extends _i2.Mock implements _i11.EventService { /// A class which mocks [ChatService]. /// /// See the documentation for Mockito's code generation for more information. -class MockChatService extends _i2.Mock implements _i22.ChatService { +class MockChatService extends _i2.Mock implements _i23.ChatService { @override _i5.Stream<_i3.QueryResult> get chatStream => (super.noSuchMethod( Invocation.getter(#chatStream), @@ -1541,20 +1613,20 @@ class MockChatService extends _i2.Mock implements _i22.ChatService { ); @override - _i5.Stream<_i23.ChatListTileDataModel> get chatListStream => + _i5.Stream<_i24.ChatListTileDataModel> get chatListStream => (super.noSuchMethod( Invocation.getter(#chatListStream), - returnValue: _i5.Stream<_i23.ChatListTileDataModel>.empty(), + returnValue: _i5.Stream<_i24.ChatListTileDataModel>.empty(), returnValueForMissingStub: - _i5.Stream<_i23.ChatListTileDataModel>.empty(), - ) as _i5.Stream<_i23.ChatListTileDataModel>); + _i5.Stream<_i24.ChatListTileDataModel>.empty(), + ) as _i5.Stream<_i24.ChatListTileDataModel>); @override - _i5.Stream<_i24.ChatMessage> get chatMessagesStream => (super.noSuchMethod( + _i5.Stream<_i25.ChatMessage> get chatMessagesStream => (super.noSuchMethod( Invocation.getter(#chatMessagesStream), - returnValue: _i5.Stream<_i24.ChatMessage>.empty(), - returnValueForMissingStub: _i5.Stream<_i24.ChatMessage>.empty(), - ) as _i5.Stream<_i24.ChatMessage>); + returnValue: _i5.Stream<_i25.ChatMessage>.empty(), + returnValueForMissingStub: _i5.Stream<_i25.ChatMessage>.empty(), + ) as _i5.Stream<_i25.ChatMessage>); @override _i5.Future sendMessageToDirectChat( @@ -1598,7 +1670,7 @@ class MockChatService extends _i2.Mock implements _i22.ChatService { /// A class which mocks [UserConfig]. /// /// See the documentation for Mockito's code generation for more information. -class MockUserConfig extends _i2.Mock implements _i25.UserConfig { +class MockUserConfig extends _i2.Mock implements _i26.UserConfig { @override _i5.Stream<_i6.OrgInfo> get currentOrgInfoStream => (super.noSuchMethod( Invocation.getter(#currentOrgInfoStream), @@ -1809,7 +1881,7 @@ class MockUserConfig extends _i2.Mock implements _i25.UserConfig { /// A class which mocks [AppLanguage]. /// /// See the documentation for Mockito's code generation for more information. -class MockAppLanguage extends _i2.Mock implements _i26.AppLanguage { +class MockAppLanguage extends _i2.Mock implements _i27.AppLanguage { @override bool get isTest => (super.noSuchMethod( Invocation.getter(#isTest), @@ -2032,7 +2104,7 @@ class MockAppLanguage extends _i2.Mock implements _i26.AppLanguage { /// /// See the documentation for Mockito's code generation for more information. class MockSignupDetailsViewModel extends _i2.Mock - implements _i27.SignupDetailsViewModel { + implements _i28.SignupDetailsViewModel { @override _i1.GlobalKey<_i1.FormState> get formKey => (super.noSuchMethod( Invocation.getter(#formKey), @@ -2715,7 +2787,7 @@ class MockDataBaseMutationFunctions extends _i2.Mock /// /// See the documentation for Mockito's code generation for more information. class MockOrganizationService extends _i2.Mock - implements _i28.OrganizationService { + implements _i29.OrganizationService { @override _i5.Future> getOrgMembersList(String? orgId) => (super.noSuchMethod( @@ -2733,7 +2805,7 @@ class MockOrganizationService extends _i2.Mock /// /// See the documentation for Mockito's code generation for more information. class MockExploreEventsViewModel extends _i2.Mock - implements _i29.ExploreEventsViewModel { + implements _i30.ExploreEventsViewModel { @override bool get demoMode => (super.noSuchMethod( Invocation.getter(#demoMode), @@ -2937,7 +3009,7 @@ class MockExploreEventsViewModel extends _i2.Mock /// /// See the documentation for Mockito's code generation for more information. class MockOrganizationFeedViewModel extends _i2.Mock - implements _i30.OrganizationFeedViewModel { + implements _i31.OrganizationFeedViewModel { @override bool get istest => (super.noSuchMethod( Invocation.getter(#istest), @@ -3167,7 +3239,7 @@ class MockOrganizationFeedViewModel extends _i2.Mock /// A class which mocks [Validator]. /// /// See the documentation for Mockito's code generation for more information. -class MockValidator extends _i2.Mock implements _i31.Validator { +class MockValidator extends _i2.Mock implements _i32.Validator { @override _i5.Future validateUrlExistence(String? url) => (super.noSuchMethod( Invocation.method( @@ -3182,13 +3254,13 @@ class MockValidator extends _i2.Mock implements _i31.Validator { /// A class which mocks [QRViewController]. /// /// See the documentation for Mockito's code generation for more information. -class MockQRViewController extends _i2.Mock implements _i32.QRViewController { +class MockQRViewController extends _i2.Mock implements _i33.QRViewController { @override - _i5.Stream<_i33.Barcode> get scannedDataStream => (super.noSuchMethod( + _i5.Stream<_i34.Barcode> get scannedDataStream => (super.noSuchMethod( Invocation.getter(#scannedDataStream), - returnValue: _i5.Stream<_i33.Barcode>.empty(), - returnValueForMissingStub: _i5.Stream<_i33.Barcode>.empty(), - ) as _i5.Stream<_i33.Barcode>); + returnValue: _i5.Stream<_i34.Barcode>.empty(), + returnValueForMissingStub: _i5.Stream<_i34.Barcode>.empty(), + ) as _i5.Stream<_i34.Barcode>); @override bool get hasPermissions => (super.noSuchMethod( @@ -3198,28 +3270,28 @@ class MockQRViewController extends _i2.Mock implements _i32.QRViewController { ) as bool); @override - _i5.Future<_i34.CameraFacing> getCameraInfo() => (super.noSuchMethod( + _i5.Future<_i35.CameraFacing> getCameraInfo() => (super.noSuchMethod( Invocation.method( #getCameraInfo, [], ), returnValue: - _i5.Future<_i34.CameraFacing>.value(_i34.CameraFacing.back), + _i5.Future<_i35.CameraFacing>.value(_i35.CameraFacing.back), returnValueForMissingStub: - _i5.Future<_i34.CameraFacing>.value(_i34.CameraFacing.back), - ) as _i5.Future<_i34.CameraFacing>); + _i5.Future<_i35.CameraFacing>.value(_i35.CameraFacing.back), + ) as _i5.Future<_i35.CameraFacing>); @override - _i5.Future<_i34.CameraFacing> flipCamera() => (super.noSuchMethod( + _i5.Future<_i35.CameraFacing> flipCamera() => (super.noSuchMethod( Invocation.method( #flipCamera, [], ), returnValue: - _i5.Future<_i34.CameraFacing>.value(_i34.CameraFacing.back), + _i5.Future<_i35.CameraFacing>.value(_i35.CameraFacing.back), returnValueForMissingStub: - _i5.Future<_i34.CameraFacing>.value(_i34.CameraFacing.back), - ) as _i5.Future<_i34.CameraFacing>); + _i5.Future<_i35.CameraFacing>.value(_i35.CameraFacing.back), + ) as _i5.Future<_i35.CameraFacing>); @override _i5.Future getFlashStatus() => (super.noSuchMethod( @@ -3318,7 +3390,7 @@ class MockQRViewController extends _i2.Mock implements _i32.QRViewController { /// A class which mocks [CommentService]. /// /// See the documentation for Mockito's code generation for more information. -class MockCommentService extends _i2.Mock implements _i35.CommentService { +class MockCommentService extends _i2.Mock implements _i36.CommentService { @override _i5.Future createComments( String? postId, @@ -3351,7 +3423,7 @@ class MockCommentService extends _i2.Mock implements _i35.CommentService { /// A class which mocks [AppTheme]. /// /// See the documentation for Mockito's code generation for more information. -class MockAppTheme extends _i2.Mock implements _i36.AppTheme { +class MockAppTheme extends _i2.Mock implements _i37.AppTheme { @override String get key => (super.noSuchMethod( Invocation.getter(#key), @@ -3475,7 +3547,7 @@ class MockAppTheme extends _i2.Mock implements _i36.AppTheme { /// /// See the documentation for Mockito's code generation for more information. class MockCreateEventViewModel extends _i2.Mock - implements _i37.CreateEventViewModel { + implements _i38.CreateEventViewModel { @override _i1.TextEditingController get eventTitleTextController => (super.noSuchMethod( Invocation.getter(#eventTitleTextController), @@ -4147,15 +4219,15 @@ class MockCreateEventViewModel extends _i2.Mock ); @override - _i5.Future> fetchVenues() => (super.noSuchMethod( + _i5.Future> fetchVenues() => (super.noSuchMethod( Invocation.method( #fetchVenues, [], ), - returnValue: _i5.Future>.value(<_i38.Venue>[]), + returnValue: _i5.Future>.value(<_i39.Venue>[]), returnValueForMissingStub: - _i5.Future>.value(<_i38.Venue>[]), - ) as _i5.Future>); + _i5.Future>.value(<_i39.Venue>[]), + ) as _i5.Future>); @override void setState(_i14.ViewState? viewState) => super.noSuchMethod( @@ -4207,7 +4279,7 @@ class MockCreateEventViewModel extends _i2.Mock /// /// See the documentation for Mockito's code generation for more information. class MockDirectChatViewModel extends _i2.Mock - implements _i39.DirectChatViewModel { + implements _i40.DirectChatViewModel { @override _i1.GlobalKey<_i1.AnimatedListState> get listKey => (super.noSuchMethod( Invocation.getter(#listKey), @@ -4247,19 +4319,19 @@ class MockDirectChatViewModel extends _i2.Mock ); @override - List<_i23.ChatListTileDataModel> get chats => (super.noSuchMethod( + List<_i24.ChatListTileDataModel> get chats => (super.noSuchMethod( Invocation.getter(#chats), - returnValue: <_i23.ChatListTileDataModel>[], - returnValueForMissingStub: <_i23.ChatListTileDataModel>[], - ) as List<_i23.ChatListTileDataModel>); + returnValue: <_i24.ChatListTileDataModel>[], + returnValueForMissingStub: <_i24.ChatListTileDataModel>[], + ) as List<_i24.ChatListTileDataModel>); @override - Map> get chatMessagesByUser => + Map> get chatMessagesByUser => (super.noSuchMethod( Invocation.getter(#chatMessagesByUser), - returnValue: >{}, - returnValueForMissingStub: >{}, - ) as Map>); + returnValue: >{}, + returnValueForMissingStub: >{}, + ) as Map>); @override _i14.ViewState get state => (super.noSuchMethod( @@ -4386,24 +4458,24 @@ class MockDirectChatViewModel extends _i2.Mock /// A class which mocks [ImageCropper]. /// /// See the documentation for Mockito's code generation for more information. -class MockImageCropper extends _i2.Mock implements _i40.ImageCropper { +class MockImageCropper extends _i2.Mock implements _i41.ImageCropper { @override - _i5.Future<_i41.CroppedFile?> cropImage({ + _i5.Future<_i42.CroppedFile?> cropImage({ required String? sourcePath, int? maxWidth, int? maxHeight, - _i41.CropAspectRatio? aspectRatio, - List<_i41.CropAspectRatioPreset>? aspectRatioPresets = const [ - _i41.CropAspectRatioPreset.original, - _i41.CropAspectRatioPreset.square, - _i41.CropAspectRatioPreset.ratio3x2, - _i41.CropAspectRatioPreset.ratio4x3, - _i41.CropAspectRatioPreset.ratio16x9, + _i42.CropAspectRatio? aspectRatio, + List<_i42.CropAspectRatioPreset>? aspectRatioPresets = const [ + _i42.CropAspectRatioPreset.original, + _i42.CropAspectRatioPreset.square, + _i42.CropAspectRatioPreset.ratio3x2, + _i42.CropAspectRatioPreset.ratio4x3, + _i42.CropAspectRatioPreset.ratio16x9, ], - _i41.CropStyle? cropStyle = _i41.CropStyle.rectangle, - _i41.ImageCompressFormat? compressFormat = _i41.ImageCompressFormat.jpg, + _i42.CropStyle? cropStyle = _i42.CropStyle.rectangle, + _i42.ImageCompressFormat? compressFormat = _i42.ImageCompressFormat.jpg, int? compressQuality = 90, - List<_i41.PlatformUiSettings>? uiSettings, + List<_i42.PlatformUiSettings>? uiSettings, }) => (super.noSuchMethod( Invocation.method( @@ -4421,19 +4493,19 @@ class MockImageCropper extends _i2.Mock implements _i40.ImageCropper { #uiSettings: uiSettings, }, ), - returnValue: _i5.Future<_i41.CroppedFile?>.value(), - returnValueForMissingStub: _i5.Future<_i41.CroppedFile?>.value(), - ) as _i5.Future<_i41.CroppedFile?>); + returnValue: _i5.Future<_i42.CroppedFile?>.value(), + returnValueForMissingStub: _i5.Future<_i42.CroppedFile?>.value(), + ) as _i5.Future<_i42.CroppedFile?>); @override - _i5.Future<_i41.CroppedFile?> recoverImage() => (super.noSuchMethod( + _i5.Future<_i42.CroppedFile?> recoverImage() => (super.noSuchMethod( Invocation.method( #recoverImage, [], ), - returnValue: _i5.Future<_i41.CroppedFile?>.value(), - returnValueForMissingStub: _i5.Future<_i41.CroppedFile?>.value(), - ) as _i5.Future<_i41.CroppedFile?>); + returnValue: _i5.Future<_i42.CroppedFile?>.value(), + returnValueForMissingStub: _i5.Future<_i42.CroppedFile?>.value(), + ) as _i5.Future<_i42.CroppedFile?>); } /// A class which mocks [ImagePicker]. diff --git a/test/helpers/test_locator.dart b/test/helpers/test_locator.dart index ecd24a0e6..746a8608c 100644 --- a/test/helpers/test_locator.dart +++ b/test/helpers/test_locator.dart @@ -31,6 +31,7 @@ import 'package:talawa/view_model/after_auth_view_models/event_view_models/creat import 'package:talawa/view_model/after_auth_view_models/event_view_models/edit_event_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/manage_volunteer_group_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart'; @@ -121,7 +122,7 @@ void testSetupLocator() { locator.registerFactory(() => OrganizationFeedViewModel()); locator.registerFactory(() => SetUrlViewModel()); locator.registerFactory(() => LoginViewModel()); - + locator.registerFactory(() => ManageVolunteerGroupViewModel()); locator.registerFactory(() => SelectOrganizationViewModel()); locator.registerFactory(() => AccessScreenViewModel()); locator.registerFactory(() => SignupDetailsViewModel()); diff --git a/test/model_tests/events/event_volunteer_group_test.dart b/test/model_tests/events/event_volunteer_group_test.dart new file mode 100644 index 000000000..48f88076b --- /dev/null +++ b/test/model_tests/events/event_volunteer_group_test.dart @@ -0,0 +1,116 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; +import 'package:talawa/models/user/user_info.dart'; + +void main() { + group('Test EventVolunteerGroup Model', () { + test('Test EventVolunteerGroup fromJson', () { + final User creator = User(id: "fakeCreatorId"); + final User leader = User(id: "fakeLeaderId"); + final List volunteers = [ + EventVolunteer(id: "volunteer1"), + EventVolunteer(id: "volunteer2"), + ]; + + final event = Event( + id: 'fakeEventId', + title: 'Sample Event', + description: 'Sample Description', + location: 'Sample Location', + startDate: '2024-08-11', + endDate: '2024-08-12', + startTime: '10:00 AM', + endTime: '12:00 PM', + ); + + final eventVolunteerGroup = EventVolunteerGroup( + id: 'group1', + createdAt: '2024-08-01', + creator: creator, + event: event, + leader: leader, + name: 'Group Name', + updatedAt: '2024-08-05', + volunteers: volunteers, + volunteersRequired: 5, + ); + + final eventVolunteerGroupJson = { + '_id': 'group1', + 'createdAt': '2024-08-01', + 'creator': { + '_id': 'fakeCreatorId', + }, + 'event': { + '_id': 'fakeEventId', + 'title': 'Sample Event', + 'description': 'Sample Description', + 'location': 'Sample Location', + 'startDate': '2024-08-11', + 'endDate': '2024-08-12', + 'startTime': '10:00 AM', + 'endTime': '12:00 PM', + }, + 'leader': { + '_id': 'fakeLeaderId', + }, + 'name': 'Group Name', + 'updatedAt': '2024-08-05', + 'volunteers': [ + {'_id': 'volunteer1'}, + {'_id': 'volunteer2'}, + ], + 'volunteersRequired': 5, + }; + + final eventVolunteerGroupFromJson = + EventVolunteerGroup.fromJson(eventVolunteerGroupJson); + + expect(eventVolunteerGroup.id, eventVolunteerGroupFromJson.id); + + expect( + eventVolunteerGroup.createdAt, + eventVolunteerGroupFromJson.createdAt, + ); + + expect( + eventVolunteerGroup.creator?.id, + eventVolunteerGroupFromJson.creator?.id, + ); + + expect( + eventVolunteerGroup.event?.id, + eventVolunteerGroupFromJson.event?.id, + ); + + expect( + eventVolunteerGroup.leader?.id, + eventVolunteerGroupFromJson.leader?.id, + ); + + expect(eventVolunteerGroup.name, eventVolunteerGroupFromJson.name); + + expect( + eventVolunteerGroup.updatedAt, + eventVolunteerGroupFromJson.updatedAt, + ); + + expect( + eventVolunteerGroup.volunteersRequired, + eventVolunteerGroupFromJson.volunteersRequired, + ); + + expect( + eventVolunteerGroup.volunteers?.length, + eventVolunteerGroupFromJson.volunteers?.length, + ); + + expect( + eventVolunteerGroup.volunteers?[0].id, + eventVolunteerGroupFromJson.volunteers?[0].id, + ); + }); + }); +} diff --git a/test/model_tests/events/event_volunteer_test.dart b/test/model_tests/events/event_volunteer_test.dart new file mode 100644 index 000000000..e232e2dd8 --- /dev/null +++ b/test/model_tests/events/event_volunteer_test.dart @@ -0,0 +1,78 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; +import 'package:talawa/models/user/user_info.dart'; + +void main() { + group('Test EventVolunteer Model', () { + test('Test EventVolunteer fromJson', () { + final User creator = User(id: "fakeCreatorId"); + final Event event = Event( + id: 'fakeEventId', + title: 'Sample Event', + description: 'Sample Description', + location: 'Sample Location', + startDate: '2024-08-11', + endDate: '2024-08-12', + startTime: '10:00 AM', + endTime: '12:00 PM', + ); + final EventVolunteerGroup group = EventVolunteerGroup( + id: 'group1', + name: 'Group Name', + ); + final User user = User(id: "fakeUserId"); + + final eventVolunteer = EventVolunteer( + id: 'volunteer1', + creator: creator, + event: event, + group: group, + isAssigned: true, + isInvited: false, + response: 'Accepted', + user: user, + ); + final eventVolunteerJson = { + '_id': 'volunteer1', + 'creator': { + '_id': 'fakeCreatorId', + }, + 'event': { + '_id': 'fakeEventId', + 'title': 'Sample Event', + 'description': 'Sample Description', + 'location': 'Sample Location', + 'startDate': '2024-08-11', + 'endDate': '2024-08-12', + 'startTime': '10:00 AM', + 'endTime': '12:00 PM', + }, + 'group': { + '_id': 'group1', + 'name': 'Group Name', + }, + 'isAssigned': true, + 'isInvited': false, + 'response': 'Accepted', + 'user': { + '_id': 'fakeUserId', + }, + }; + + final eventVolunteerFromJson = + EventVolunteer.fromJson(eventVolunteerJson); + + // Verifying that all fields were correctly deserialized + expect(eventVolunteer.id, eventVolunteerFromJson.id); + expect(eventVolunteer.creator?.id, eventVolunteerFromJson.creator?.id); + expect(eventVolunteer.event?.id, eventVolunteerFromJson.event?.id); + expect(eventVolunteer.group?.id, eventVolunteerFromJson.group?.id); + expect(eventVolunteer.isAssigned, eventVolunteerFromJson.isAssigned); + expect(eventVolunteer.isInvited, eventVolunteerFromJson.isInvited); + expect(eventVolunteer.response, eventVolunteerFromJson.response); + expect(eventVolunteer.user?.id, eventVolunteerFromJson.user?.id); + }); + }); +} diff --git a/test/router_test.dart b/test/router_test.dart index 8139d8b81..a66257d30 100644 --- a/test/router_test.dart +++ b/test/router_test.dart @@ -8,6 +8,7 @@ import 'package:talawa/constants/routing_constants.dart'; import 'package:talawa/locator.dart'; import 'package:talawa/main.dart'; import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; import 'package:talawa/models/mainscreen_navigation_args.dart'; import 'package:talawa/models/organization/org_info.dart'; import 'package:talawa/models/post/post_model.dart'; @@ -16,6 +17,7 @@ import 'package:talawa/router.dart'; import 'package:talawa/splash_screen.dart'; import 'package:talawa/view_model/after_auth_view_models/chat_view_models/direct_chat_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/create_event_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart'; import 'package:talawa/views/after_auth_screens/add_post_page.dart'; import 'package:talawa/views/after_auth_screens/app_settings/app_settings_page.dart'; import 'package:talawa/views/after_auth_screens/chat/chat_message_screen.dart'; @@ -25,6 +27,8 @@ import 'package:talawa/views/after_auth_screens/events/create_event_page.dart'; import 'package:talawa/views/after_auth_screens/events/edit_event_page.dart'; import 'package:talawa/views/after_auth_screens/events/event_calendar.dart'; import 'package:talawa/views/after_auth_screens/events/explore_events.dart'; +import 'package:talawa/views/after_auth_screens/events/manage_volunteer_group.dart'; +import 'package:talawa/views/after_auth_screens/events/volunteer_groups_screen.dart'; import 'package:talawa/views/after_auth_screens/feed/individual_post.dart'; import 'package:talawa/views/after_auth_screens/feed/organization_feed.dart'; import 'package:talawa/views/after_auth_screens/feed/pinned_post_page.dart'; @@ -409,5 +413,36 @@ void main() { expect(widget, isA()); } }); + testWidgets('Test for default volunteer groups screen route', + (WidgetTester tester) async { + final route = generateRoute( + RouteSettings( + name: Routes.volunteerGroupScreen, + arguments: [Event(), EventInfoViewModel()], + ), + ); + expect(route, isA()); + if (route is MaterialPageRoute) { + final builder = route.builder; + final widget = builder(MockBuildContext()); + expect(widget, isA()); + } + }); + + testWidgets('Test for default manage volunteer group screen route', + (WidgetTester tester) async { + final route = generateRoute( + RouteSettings( + name: Routes.manageVolunteerGroup, + arguments: [Event(), EventVolunteerGroup()], + ), + ); + expect(route, isA()); + if (route is MaterialPageRoute) { + final builder = route.builder; + final widget = builder(MockBuildContext()); + expect(widget, isA()); + } + }); }); } diff --git a/test/service_tests/database_mutations_function_test.dart b/test/service_tests/database_mutations_function_test.dart index 835b0c274..4d92040b4 100644 --- a/test/service_tests/database_mutations_function_test.dart +++ b/test/service_tests/database_mutations_function_test.dart @@ -363,10 +363,19 @@ void main() async { test('Testing gqlAuthQuery function without exception', () async { final String query = Queries().fetchOrgDetailsById('XYZ'); - when(locator().query(QueryOptions(document: gql(query)))) - .thenAnswer( + when( + locator().query( + QueryOptions( + document: gql(query), + fetchPolicy: FetchPolicy.networkOnly, + ), + ), + ).thenAnswer( (_) async => QueryResult( - options: QueryOptions(document: gql(query)), + options: QueryOptions( + document: gql(query), + fetchPolicy: FetchPolicy.networkOnly, + ), data: { 'organizations': [ { @@ -397,10 +406,19 @@ void main() async { test('Testing gqlAuthQuery with false exception', () async { final String query = Queries().fetchOrgDetailsById('XYZ'); - when(locator().query(QueryOptions(document: gql(query)))) - .thenAnswer( + when( + locator().query( + QueryOptions( + document: gql(query), + fetchPolicy: FetchPolicy.networkOnly, + ), + ), + ).thenAnswer( (_) async => QueryResult( - options: QueryOptions(document: gql(query)), + options: QueryOptions( + document: gql(query), + fetchPolicy: FetchPolicy.networkOnly, + ), exception: OperationException(graphqlErrors: [userNotFound]), source: QueryResultSource.network, ), @@ -443,20 +461,37 @@ void main() async { } } - when(locator().query(QueryOptions(document: gql(query)))) - .thenAnswer( + when( + locator().query( + QueryOptions( + document: gql(query), + fetchPolicy: FetchPolicy.networkOnly, + ), + ), + ).thenAnswer( (_) async => QueryResult( - options: QueryOptions(document: gql(query)), + options: QueryOptions( + document: gql(query), + fetchPolicy: FetchPolicy.networkOnly, + ), exception: exp2()['val'], source: QueryResultSource.network, ), ); when( - locator().mutate(MutationOptions(document: gql(query2))), + locator().mutate( + MutationOptions( + document: gql(query2), + fetchPolicy: FetchPolicy.networkOnly, + ), + ), ).thenAnswer( (_) async => QueryResult( - options: QueryOptions(document: gql(query2)), + options: QueryOptions( + document: gql(query2), + fetchPolicy: FetchPolicy.networkOnly, + ), data: { 'refreshToken': { 'accessToken': 'testtoken', @@ -468,10 +503,18 @@ void main() async { ); when( - locator().mutate(MutationOptions(document: gql(query3))), + locator().mutate( + MutationOptions( + document: gql(query3), + fetchPolicy: FetchPolicy.networkOnly, + ), + ), ).thenAnswer( (_) async => QueryResult( - options: QueryOptions(document: gql(query3)), + options: QueryOptions( + document: gql(query3), + fetchPolicy: FetchPolicy.networkOnly, + ), data: { 'refreshToken': { 'accessToken': 'testtoken', diff --git a/test/service_tests/event_service_test.dart b/test/service_tests/event_service_test.dart index da61d3054..9697175e1 100644 --- a/test/service_tests/event_service_test.dart +++ b/test/service_tests/event_service_test.dart @@ -1,8 +1,11 @@ +// ignore_for_file: avoid_dynamic_calls + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:mockito/mockito.dart'; import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; import 'package:talawa/models/organization/org_info.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/services/event_service.dart'; @@ -247,5 +250,206 @@ void main() { final model = EventService(); expect(model.eventStream, isA>>()); }); + test('Test createVolunteerGroup method', () async { + final dataBaseMutationFunctions = locator(); + const query = ''; + final Map variables = { + 'name': 'Volunteer Group 1', + 'eventId': 'eventId1', + }; + when( + dataBaseMutationFunctions.gqlAuthMutation( + EventQueries().createVolunteerGroup(), + variables: {'data': variables}, + ), + ).thenAnswer( + (realInvocation) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'createVolunteerGroup': { + '_id': 'groupId1', + 'name': 'Volunteer Group 1', + }, + }, + source: QueryResultSource.network, + ), + ); + + final service = EventService(); + final result = await service.createVolunteerGroup(variables); + + expect(result, isNotNull); + expect( + (result as QueryResult).data!['createVolunteerGroup']['_id'], + 'groupId1', + ); + expect( + result.data!['createVolunteerGroup']['name'], + 'Volunteer Group 1', + ); + }); + + test('Test removeVolunteerGroup method', () async { + final dataBaseMutationFunctions = locator(); + const query = ''; + final variables = {'groupId': 'groupId123'}; + when( + dataBaseMutationFunctions.gqlAuthMutation( + EventQueries().removeEventVolunteerGroup(), + variables: variables, + ), + ).thenAnswer( + (realInvocation) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'removeVolunteerGroup': { + '_id': 'groupId123', + 'name': 'Volunteer Group 1', + }, + }, + source: QueryResultSource.network, + ), + ); + + final service = EventService(); + final result = await service.removeVolunteerGroup(variables); + expect(result, isA()); + expect(result.data!['removeVolunteerGroup']['_id'], 'groupId123'); + }); + + test('Test addVolunteerToGroup method', () async { + final dataBaseMutationFunctions = locator(); + const query = ''; + final variables = { + 'groupId': 'groupId123', + 'volunteerId': 'volunteerId123', + }; + when( + dataBaseMutationFunctions.gqlAuthMutation( + EventQueries().addVolunteerToGroup(), + variables: {'data': variables}, + ), + ).thenAnswer( + (realInvocation) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'addVolunteerToGroup': { + '_id': 'volunteerId123', + 'name': 'Volunteer Name', + }, + }, + source: QueryResultSource.network, + ), + ); + + final service = EventService(); + final result = await service.addVolunteerToGroup(variables); + expect(result, isA()); + expect(result.data!['addVolunteerToGroup']['_id'], 'volunteerId123'); + }); + + test('Test removeVolunteerFromGroup method', () async { + final dataBaseMutationFunctions = locator(); + const query = ''; + final variables = {'volunteerId': 'volunteerId123'}; + when( + dataBaseMutationFunctions.gqlAuthMutation( + EventQueries().removeVolunteerMutation(), + variables: variables, + ), + ).thenAnswer( + (realInvocation) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'removeVolunteer': { + '_id': 'volunteerId123', + 'name': 'Volunteer Name', + }, + }, + source: QueryResultSource.network, + ), + ); + + final service = EventService(); + final result = await service.removeVolunteerFromGroup(variables); + expect(result, isA()); + expect(result.data!['removeVolunteer']['_id'], 'volunteerId123'); + }); + + test('Test updateVolunteerGroup method', () async { + final dataBaseMutationFunctions = locator(); + const query = ''; + final variables = { + 'groupId': 'groupId123', + 'name': 'Updated Volunteer Group Name', + }; + when( + dataBaseMutationFunctions.gqlAuthMutation( + EventQueries().updateVolunteerGroupMutation(), + variables: variables, + ), + ).thenAnswer( + (realInvocation) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'updateVolunteerGroup': { + '_id': 'groupId123', + 'name': 'Updated Volunteer Group Name', + }, + }, + source: QueryResultSource.network, + ), + ); + + final service = EventService(); + final result = await service.updateVolunteerGroup(variables); + expect(result, isA()); + expect(result.data!['updateVolunteerGroup']['_id'], 'groupId123'); + expect( + result.data!['updateVolunteerGroup']['name'], + 'Updated Volunteer Group Name', + ); + }); + + test('Test fetchVolunteerGroupsByEvent method', () async { + final dataBaseMutationFunctions = locator(); + const query = ''; + const eventId = 'eventId123'; + when( + dataBaseMutationFunctions.gqlAuthQuery( + EventQueries().fetchVolunteerGroups(), + variables: { + "where": {"eventId": eventId}, + }, + ), + ).thenAnswer( + (realInvocation) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'getEventVolunteerGroups': [ + { + '_id': 'groupId1', + 'name': 'Volunteer Group 1', + 'eventId': eventId, + }, + { + '_id': 'groupId2', + 'name': 'Volunteer Group 2', + 'eventId': eventId, + }, + ], + }, + source: QueryResultSource.network, + ), + ); + + final service = EventService(); + final result = await service.fetchVolunteerGroupsByEvent(eventId); + + expect(result, isA>()); + expect(result.length, 2); + expect(result[0].id, 'groupId1'); + expect(result[1].id, 'groupId2'); + }); }); } diff --git a/test/utils/event_queries_test.dart b/test/utils/event_queries_test.dart index 83342ae37..2de587fab 100644 --- a/test/utils/event_queries_test.dart +++ b/test/utils/event_queries_test.dart @@ -147,5 +147,142 @@ void main() { final fnData = EventQueries().updateEvent(eventId: "sampleID"); expect(fnData, data); }); + test("Check if createVolunteerGroup works correctly", () { + const data = ''' + mutation CreateEventVolunteerGroup(\$data: EventVolunteerGroupInput!) { + createEventVolunteerGroup(data: \$data) { + _id + name + volunteers{ + _id + } + createdAt + volunteersRequired + creator{ + _id + } + } + } + '''; + + final fnData = EventQueries().createVolunteerGroup(); + expect(fnData, data); + }); + + test("Check if removeVolunteerGroup works correctly", () { + const expected = ''' + mutation RemoveEventVolunteerGroup(\$id: ID!) { + removeEventVolunteerGroup(id: \$id) { + _id + name + } + } + '''; + + final actual = EventQueries().removeEventVolunteerGroup().trim(); + expect(actual, expected.trim()); + }); + + test("Check if addVolunteerToGroup works correctly", () { + const expected = ''' +mutation CreateEventVolunteer(\$data: EventVolunteerInput!) { + createEventVolunteer(data: \$data) { + _id + isAssigned + response + creator { + _id + } + group { + _id + name + } + isInvited + user { + _id + firstName + lastName + } + } + } + '''; + + final actual = EventQueries() + .addVolunteerToGroup() + .replaceAll(' ', '') + .replaceAll('\n', '') + .replaceAll('\t', ''); + + expect( + actual, + expected.replaceAll(' ', '').replaceAll('\n', '').replaceAll('\t', ''), + ); + }); + test("Check if removeVolunteerFromGroup works correctly", () { + const expected = ''' + mutation RemoveEventVolunteer(\$id: ID!) { + removeEventVolunteer(id: \$id) { + _id + } + } + '''; + + final actual = EventQueries().removeVolunteerMutation().trim(); + expect(actual, expected.trim()); + }); + + test("Check if updateVolunteerGroup works correctly", () { + const expected = ''' + mutation UpdateEventVolunteerGroup(\$id: ID!, \$data: UpdateEventVolunteerGroupInput!) { + updateEventVolunteerGroup(id: \$id, data: \$data) { + _id + name + volunteersRequired + } + } + '''; + + final actual = EventQueries() + .updateVolunteerGroupMutation() + .replaceAll(' ', '') + .replaceAll('\n', '') + .replaceAll('\t', ''); + expect( + actual, + expected.replaceAll(' ', '').replaceAll('\n', '').replaceAll('\t', ''), + ); + }); + + test("Check if fetchVolunteerGroupsByEvent works correctly", () { + const expected = ''' + query GetEventVolunteerGroups(\$where: EventVolunteerGroupWhereInput) { + getEventVolunteerGroups(where: \$where) { + _id + name + volunteersRequired + createdAt + volunteers { + _id + response + user { + _id + firstName + lastName + } + } + } + } + '''; + + final actual = EventQueries() + .fetchVolunteerGroups() + .replaceAll(' ', '') + .replaceAll('\n', '') + .replaceAll('\t', ''); + expect( + actual, + expected.replaceAll(' ', '').replaceAll('\n', '').replaceAll('\t', ''), + ); + }); }); } diff --git a/test/view_model_tests/after_auth_view_model_tests/event_view_model_tests/event_info_view_model_test.dart b/test/view_model_tests/after_auth_view_model_tests/event_view_model_tests/event_info_view_model_test.dart index 979a7158e..fd0a2c510 100644 --- a/test/view_model_tests/after_auth_view_model_tests/event_view_model_tests/event_info_view_model_test.dart +++ b/test/view_model_tests/after_auth_view_model_tests/event_view_model_tests/event_info_view_model_test.dart @@ -3,10 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:mockito/mockito.dart'; import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; import 'package:talawa/services/graphql_config.dart'; import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/event_queries.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; import '../../../helpers/test_helpers.dart'; @@ -85,5 +88,99 @@ void main() { model.event = event3; expect(model.getFabTitle(), "Registered"); }); + test("Test createVolunteerGroup success", () async { + final Event event1 = Event(id: "1"); + model.event = event1; + + final eventService = getAndRegisterEventService(); + final mockResult = { + 'createEventVolunteerGroup': { + '_id': 'group1', + 'name': 'Group 1', + 'volunteersRequired': 10, + 'createdAt': '2024-01-01T00:00:00Z', + 'creator': {'_id': 'creator1'}, + }, + }; + + when( + eventService.createVolunteerGroup({ + 'eventId': "1", + 'name': 'Group 1', + 'volunteersRequired': 10, + }), + ).thenAnswer( + (_) async => QueryResult( + data: mockResult, + source: QueryResultSource.network, + options: QueryOptions( + document: gql(EventQueries().createVolunteerGroup()), + ), + ), + ); + + final newGroup = await model.createVolunteerGroup(event1, 'Group 1', 10); + + expect(newGroup, isNotNull); + expect(newGroup!.name, 'Group 1'); + expect(model.volunteerGroups.length, 1); + expect(model.volunteerGroups.first.name, 'Group 1'); + }); + + test("Test createVolunteerGroup Failure", () async { + final Event event1 = Event(id: "1"); + model.event = event1; + + final eventService = getAndRegisterEventService(); + + when( + eventService.createVolunteerGroup({ + 'eventId': "1", + 'name': 'Group 1', + 'volunteersRequired': 10, + }), + ).thenThrow(Exception('Failed to create new volunteer group')); + + final newGroup = await model.createVolunteerGroup(event1, 'Group 1', 10); + + expect(newGroup, isNull); + }); + + test("Test fetchVolunteerGroups success", () async { + final Event event1 = Event(id: "1"); + model.event = event1; + + final eventService = getAndRegisterEventService(); + final mockResult = [ + EventVolunteerGroup( + id: 'group1', + name: 'Group 1', + volunteersRequired: 10, + createdAt: '2024-01-01T00:00:00Z', + ), + ]; + + when(eventService.fetchVolunteerGroupsByEvent("1")) + .thenAnswer((_) async => mockResult); + + await model.fetchVolunteerGroups('1'); + + expect(model.volunteerGroups.length, 1); + expect(model.volunteerGroups.first.name, 'Group 1'); + }); + + test("Test fetchVolunteerGroups failure", () async { + final Event event1 = Event(id: "1"); + model.event = event1; + model.volunteerGroups.clear(); + + final eventService = getAndRegisterEventService(); + when(eventService.fetchVolunteerGroupsByEvent("1")) + .thenThrow(Exception('Failed to fetch volunteer groups')); + + await model.fetchVolunteerGroups('1'); + + expect(model.volunteerGroups.length, 0); + }); }); } diff --git a/test/view_model_tests/after_auth_view_model_tests/event_view_model_tests/manage_volunteer_group_view_model_test.dart b/test/view_model_tests/after_auth_view_model_tests/event_view_model_tests/manage_volunteer_group_view_model_test.dart new file mode 100644 index 000000000..1323a96c2 --- /dev/null +++ b/test/view_model_tests/after_auth_view_model_tests/event_view_model_tests/manage_volunteer_group_view_model_test.dart @@ -0,0 +1,159 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; +import 'package:talawa/services/event_service.dart'; +import 'package:talawa/utils/event_queries.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/manage_volunteer_group_view_model.dart'; +import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_locator.dart'; + +void main() { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + testSetupLocator(); + registerServices(); + }); + + tearDownAll(() { + unregisterServices(); + }); + + group('ManageVolunteerGroupViewModel Tests', () { + final model = ManageVolunteerGroupViewModel(); + + test("Test initialization", () async { + final Event event = Event(id: "1"); + final EventVolunteerGroup group = + EventVolunteerGroup(id: "group1", volunteers: []); + + await model.initialize(event, group); + + expect(model.event.id, "1"); + expect(model.volunteers, isEmpty); + }); + + test("Test getCurrentOrgUsersList success", () async { + final users = await model.getCurrentOrgUsersList(); + expect(users.length, 2); + expect(users[0].id, "fakeUser1"); + }); + + test("Test addVolunteerToGroup success", () async { + final mockEventService = locator(); + + final mockResult = { + 'createEventVolunteer': { + '_id': 'volunteer1', + }, + }; + + when( + mockEventService.addVolunteerToGroup({ + 'eventId': "1", + 'userId': "volunteer1", + 'groupId': "group1", + }), + ).thenAnswer( + (_) async => QueryResult( + data: mockResult, + source: QueryResultSource.network, + options: + QueryOptions(document: gql(EventQueries().addVolunteerToGroup())), + ), + ); + + await model.addVolunteerToGroup("volunteer1", "1", "group1"); + + expect(model.volunteers.length, 1); + expect(model.volunteers.first.id, "volunteer1"); + }); + + test("Test removeVolunteerFromGroup success", () async { + final mockEventService = locator(); + final mockResult = { + 'removeEventVolunteer': { + 'id': 'volunteer1', + }, + }; + + when( + mockEventService.removeVolunteerFromGroup({ + 'id': 'volunteer1', + }), + ).thenAnswer( + (_) async => QueryResult( + data: mockResult, + source: QueryResultSource.network, + options: QueryOptions( + document: gql(EventQueries().removeVolunteerMutation()), + ), + ), + ); + + await model.removeVolunteerFromGroup("volunteer1"); + + expect(model.volunteers.isEmpty, true); + }); + + test("Test deleteVolunteerGroup success", () async { + final mockEventService = locator(); + final mockResult = { + 'removeEventVolunteerGroup': { + 'id': 'group1', + }, + }; + + when(mockEventService.removeVolunteerGroup({"id": "group1"})).thenAnswer( + (_) async => QueryResult( + data: mockResult, + source: QueryResultSource.network, + options: QueryOptions( + document: gql(EventQueries().removeEventVolunteerGroup()), + ), + ), + ); + + await model.deleteVolunteerGroup("group1"); + + // Assuming the method should notify listeners + verify(mockEventService.removeVolunteerGroup({"id": "group1"})).called(1); + }); + + test("Test updateVolunteerGroup success", () async { + final EventVolunteerGroup group = EventVolunteerGroup(id: "group1"); + + final mockEventService = locator(); + final mockResult = { + 'updateEventVolunteerGroup': { + 'id': 'group1', + }, + }; + + when( + mockEventService.updateVolunteerGroup({ + 'id': group.id, + 'data': { + 'eventId': "1", + 'name': "Updated Group", + 'volunteersRequired': 20, + }, + }), + ).thenAnswer( + (_) async => QueryResult( + data: mockResult, + source: QueryResultSource.network, + options: QueryOptions( + document: gql(EventQueries().updateVolunteerGroupMutation()), + ), + ), + ); + + await model.updateVolunteerGroup(group, "1", "Updated Group", 20); + + expect(group.name, "Updated Group"); + expect(group.volunteersRequired, 20); + }); + }); +} diff --git a/test/views/after_auth_screens/events/manage_volunteer_group_test.dart b/test/views/after_auth_screens/events/manage_volunteer_group_test.dart new file mode 100644 index 000000000..e17852ab4 --- /dev/null +++ b/test/views/after_auth_screens/events/manage_volunteer_group_test.dart @@ -0,0 +1,407 @@ +// 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:graphql_flutter/graphql_flutter.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/event_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/utils/event_queries.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/manage_volunteer_group_view_model.dart'; +import 'package:talawa/view_model/lang_view_model.dart'; +import 'package:talawa/views/after_auth_screens/events/manage_volunteer_group.dart'; +import 'package:talawa/views/base_view.dart'; + +import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_locator.dart'; + +Event getTestEvent({ + bool isPublic = true, + bool asAdmin = false, +}) { + return Event( + id: "1", + title: "test_event", + creator: User( + id: asAdmin ? "xzy1" : "acb1", + firstName: "ravidi", + lastName: "shaikh", + ), + isPublic: isPublic, + startDate: "00/00/0000", + endDate: "12/12/9999", + startTime: "00:00", + endTime: "24:00", + location: "iitbhu, varanasi", + description: "test_event_description", + admins: [ + User( + firstName: "ravidi_admin_one", + lastName: "shaikh_admin_one", + ), + User( + firstName: "ravidi_admin_two", + lastName: "shaikh_admin_two", + ), + ], + attendees: [ + Attendee( + id: "1", + firstName: "Test", + lastName: "User", + ), + ], + isRegisterable: true, + ); +} + +EventVolunteerGroup group1 = EventVolunteerGroup( + id: "volunteer_group", + event: Event(id: "1"), + creator: User(id: "creator_id"), + volunteers: [ + EventVolunteer( + id: "volunteer_id_1", + user: User( + firstName: "first1", + lastName: "last1", + ), + response: null, + ), + EventVolunteer( + id: "volunteer_id_2", + user: User( + firstName: "first2", + lastName: "last2", + ), + response: null, + ), + ], + volunteersRequired: 2, + name: "test_group", +); + +EventVolunteerGroup group2 = EventVolunteerGroup( + id: "volunteer_group2", + event: Event(id: "1"), + creator: User(id: "creator_id"), + volunteers: [], + volunteersRequired: 2, + name: "test_group", +); + +Widget createManageGroupScreen1(EventVolunteerGroup group) { + return BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, langModel, child) { + return BaseView( + onModelReady: (model) { + model.initialize(getTestEvent(), group); + }, + builder: (context, model, child) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: Scaffold( + body: ManageGroupScreen( + group: group, + event: getTestEvent(), + ), + ), + navigatorKey: locator().navigatorKey, + onGenerateRoute: router.generateRoute, + ); + }, + ); + }, + ); +} + +void main() { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + + testSetupLocator(); + registerServices(); + locator().test(); + }); + + tearDownAll(() { + unregisterServices(); + }); + + group("Widget Tests for ManageGroupScreen", () { + testWidgets("Check if ManageGroupScreen shows up", (tester) async { + await tester.pumpWidget(createManageGroupScreen1(group1)); + await tester.pumpAndSettle(); + + expect(find.byType(ManageGroupScreen), findsOneWidget); + }); + + testWidgets("Check if all volunteers shows up correctly", (tester) async { + await tester.pumpWidget(createManageGroupScreen1(group1)); + await tester.pumpAndSettle(); + + expect(find.byType(ManageGroupScreen), findsOneWidget); + expect(find.text('No volunteers yet'), findsNothing); + expect(find.text("first1 last1"), findsOneWidget); + expect(find.text("first2 last2"), findsOneWidget); + expect(find.text("Add Volunteers"), findsOneWidget); + }); + + testWidgets( + "Check if edit group button work properly", + (tester) async { + final mockEventService = locator(); + final mockResult = { + 'updateEventVolunteerGroup': { + 'id': 'volunteer_group', + }, + }; + + when( + mockEventService.updateVolunteerGroup({ + 'id': group1.id, + 'data': { + 'eventId': getTestEvent().id, + 'name': "Updated Group", + 'volunteersRequired': 20, + }, + }), + ).thenAnswer( + (_) async => QueryResult( + data: mockResult, + source: QueryResultSource.network, + options: QueryOptions( + document: gql(EventQueries().updateVolunteerGroupMutation()), + ), + ), + ); + await tester.pumpWidget(createManageGroupScreen1(group1)); + await tester.pumpAndSettle(); + + expect(find.byType(ManageGroupScreen), findsOneWidget); + expect(find.text('No volunteers yet'), findsNothing); + expect(find.text("first1 last1"), findsOneWidget); + expect(find.text('Add Volunteers'), findsOneWidget); + expect(find.text("Edit Group"), findsOneWidget); + + await tester.tap(find.text("Edit Group")); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsOneWidget); + + await tester.tap(find.text("Cancel")); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + + await tester.tap(find.text("Edit Group")); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsOneWidget); + + await tester.enterText( + find.byKey(const Key('groupNameField')), + 'Updated Group', + ); + + await tester.pumpAndSettle(); + await tester.enterText( + find.byKey(const Key('groupVolunteerRequiredField')), + '20', + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Save')); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + + expect(find.text('Updated Group'), findsOneWidget); + }, + ); + + testWidgets("Check add volunteer button work properly", (tester) async { + final mockEventService = locator(); + + final mockResult1 = { + 'createEventVolunteer': { + '_id': "fakeUser1", + 'user': { + '_id': "fakeUser1", + 'firstName': 'Parag', + 'lastName': 'xoxo', + }, + 'response': null, + }, + }; + + final mockResult2 = { + 'createEventVolunteer': { + '_id': "fakeUser2", + 'user': { + '_id': "fakeUser2", + 'firstName': 'Parag1', + 'lastName': 'xoxo', + }, + 'response': null, + }, + }; + + when( + mockEventService.addVolunteerToGroup({ + 'eventId': "1", + 'userId': "fakeUser1", + 'groupId': "volunteer_group", + }), + ).thenAnswer( + (_) async => QueryResult( + data: mockResult1, + source: QueryResultSource.network, + options: + QueryOptions(document: gql(EventQueries().addVolunteerToGroup())), + ), + ); + + when( + mockEventService.addVolunteerToGroup({ + 'eventId': "1", + 'userId': "fakeUser2", + 'groupId': "volunteer_group", + }), + ).thenAnswer( + (_) async => QueryResult( + data: mockResult2, + source: QueryResultSource.network, + options: + QueryOptions(document: gql(EventQueries().addVolunteerToGroup())), + ), + ); + await tester.pumpWidget(createManageGroupScreen1(group1)); + await tester.pumpAndSettle(); + + expect(find.byType(ManageGroupScreen), findsOneWidget); + expect(find.text('No volunteers yet'), findsNothing); + expect(find.text("first1 last1"), findsOneWidget); + expect(find.text('Add Volunteers'), findsOneWidget); + expect(find.text("Edit Group"), findsOneWidget); + + await tester.tap(find.text("Add Volunteers")); + await tester.pumpAndSettle(); + + expect( + find.byKey( + const Key("bottomSheetContainer"), + ), + findsOneWidget, + ); + + expect( + find.byKey( + const Key("members_list_key"), + ), + findsOneWidget, + ); + expect( + find.byKey( + const Key("checkBox0"), + ), + findsOneWidget, + ); + + await tester.tap( + find.byKey( + const Key("checkBox0"), + ), + ); + await tester.tap( + find.byKey( + const Key("checkBox1"), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text("Done")); + await tester.pumpAndSettle(); + + await tester.tap(find.text("Add Volunteers")); + await tester.pumpAndSettle(); + + expect( + find.byKey( + const Key("members_list_key"), + ), + findsNothing, + ); + }); + + testWidgets("Check if deleting volunteer work properly", (tester) async { + await tester.pumpWidget(createManageGroupScreen1(group1)); + await tester.pumpAndSettle(); + + expect(find.byType(ManageGroupScreen), findsOneWidget); + expect(find.text('No volunteers yet'), findsNothing); + expect(find.text("first1 last1"), findsOneWidget); + expect(find.text('Add Volunteers'), findsOneWidget); + expect(find.text("Edit Group"), findsOneWidget); + + expect(find.byKey(const Key("volunteers")), findsNWidgets(2)); + + await tester.tap(find.byKey(const Key("delete_volunteer0"))); + await tester.pumpAndSettle(); + }); + + testWidgets("Check if no volunteer text shows up when no volunteer", + (tester) async { + await tester.pumpWidget(createManageGroupScreen1(group2)); + await tester.pumpAndSettle(); + + expect(find.byType(ManageGroupScreen), findsOneWidget); + expect(find.text('No volunteers yet'), findsOneWidget); + expect(find.text('Add Volunteers'), findsOneWidget); + expect(find.text("Edit Group"), findsOneWidget); + }); + + testWidgets("Check if deleting volunteer group work properly", + (tester) async { + await tester.pumpWidget(createManageGroupScreen1(group1)); + await tester.pumpAndSettle(); + + expect(find.byType(ManageGroupScreen), findsOneWidget); + expect(find.text('No volunteers yet'), findsNothing); + expect(find.text("first1 last1"), findsOneWidget); + expect(find.text('Add Volunteers'), findsOneWidget); + expect(find.text("Edit Group"), findsOneWidget); + + expect(find.byKey(const Key("volunteers")), findsNWidgets(2)); + + await tester.tap(find.text('Delete Group')); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key("Delete_group_dialogue")), findsOneWidget); + + await tester.tap(find.text("Cancel")); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key("Delete_group_dialogue")), findsNothing); + + await tester.tap(find.text('Delete Group')); + await tester.pumpAndSettle(); + + await tester.tap(find.text("Delete")); + await tester.pumpAndSettle(); + }); + }); +} diff --git a/test/views/after_auth_screens/events/volunteer_groups_screen_test.dart b/test/views/after_auth_screens/events/volunteer_groups_screen_test.dart new file mode 100644 index 000000000..69ca00a3f --- /dev/null +++ b/test/views/after_auth_screens/events/volunteer_groups_screen_test.dart @@ -0,0 +1,350 @@ +// ignore_for_file: talawa_api_doc, unused_element +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/events/event_volunteer_group.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/event_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/utils/event_queries.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/event_info_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; +import 'package:talawa/view_model/lang_view_model.dart'; +import 'package:talawa/views/after_auth_screens/events/volunteer_groups_screen.dart'; +import 'package:talawa/views/base_view.dart'; + +import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_locator.dart'; + +Event getTestEvent({ + bool isPublic = false, + bool viewOnMap = true, + bool asAdmin = false, +}) { + return Event( + id: "1", + title: "test_event", + creator: User( + id: asAdmin ? "xzy1" : "acb1", + firstName: "ravidi", + lastName: "shaikh", + ), + isPublic: isPublic, + startDate: "00/00/0000", + endDate: "12/12/9999", + startTime: "00:00", + endTime: "24:00", + location: "iitbhu, varanasi", + description: "test_event_description", + admins: [ + User( + firstName: "ravidi_admin_one", + lastName: "shaikh_admin_one", + ), + User( + firstName: "ravidi_admin_two", + lastName: "shaikh_admin_two", + ), + ], + attendees: [ + Attendee( + id: "1", + firstName: "Test", + lastName: "User", + ), + ], + isRegisterable: true, + ); +} + +Widget volunteerGroupsScreen({ + bool isPublic = true, + bool viewOnMap = true, + bool asAdmin = true, +}) { + return BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, langModel, child) { + return BaseView( + onModelReady: (model) { + model.initialize( + args: { + "event": getTestEvent(), + "exploreEventViewModel": ExploreEventsViewModel(), + }, + ); + }, + builder: (context, model, child) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: Scaffold( + body: VolunteerGroupsScreen( + event: getTestEvent(), + model: EventInfoViewModel(), + ), + ), + navigatorKey: locator().navigatorKey, + onGenerateRoute: router.generateRoute, + ); + }, + ); + }, + ); +} + +void main() { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + + testSetupLocator(); + registerServices(); + + locator().test(); + }); + + tearDownAll(() { + unregisterServices(); + }); + + group("Widget Tests for VolunteerGroupscreen", () { + testWidgets("Check if VolunteerGroupscreen shows up", (tester) async { + await tester.pumpWidget(volunteerGroupsScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(VolunteerGroupsScreen), findsOneWidget); + }); + + testWidgets( + "Check if no groups are displayed when there are no volunteer groups", + (tester) async { + await tester.pumpWidget(volunteerGroupsScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(VolunteerGroupsScreen), findsOneWidget); + expect(find.text("There aren't any volunteer groups"), findsOneWidget); + }); + testWidgets("Check if groups show up", (tester) async { + final mockEventService = locator(); + final mockGroups = [ + EventVolunteerGroup(name: "Group 1", createdAt: "2027-09-08"), + EventVolunteerGroup(name: "Group 2", createdAt: "2027-09-09"), + ]; + final mockResult2 = { + 'createEventVolunteerGroup': { + '_id': "fakeUser2", + 'name': "New Group", + 'volunteersRequired': 5, + 'volunteers': [], + }, + }; + + when(mockEventService.fetchVolunteerGroupsByEvent("1")) + .thenAnswer((_) async => mockGroups); + + when( + mockEventService.createVolunteerGroup({ + 'eventId': "1", + 'name': "New Group", + 'volunteersRequired': 5, + }), + ).thenAnswer( + (realInvocation) async => QueryResult( + data: mockResult2, + source: QueryResultSource.network, + options: QueryOptions( + document: gql(EventQueries().createVolunteerGroup()), + ), + ), + ); + + await tester.pumpWidget(volunteerGroupsScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(VolunteerGroupsScreen), findsOneWidget); + expect(find.text("There aren't any volunteer groups"), findsNothing); + expect(find.byKey(const Key("group_data")), findsNWidgets(2)); + + expect(find.byKey(const Key("add_group_btn")), findsOneWidget); + await tester.tap(find.byKey(const Key("add_group_btn"))); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key("add_grp_dialogue")), findsOneWidget); + + await tester.enterText( + find.byKey(const Key("group_name_field")), + "New Group", + ); + await tester.enterText( + find.byKey(const Key("volunteers_required_field")), + "5", + ); + + await tester.tap(find.text('Create Group')); + await tester.pumpAndSettle(); + + expect( + find.text('Failed to create group'), + findsNothing, + ); + }); + testWidgets("Check if edit group icon show up", (tester) async { + final mockEventService = locator(); + final mockGroups = [ + EventVolunteerGroup( + name: "Group 1", + createdAt: "2027-09-08", + volunteersRequired: 5, + ), + EventVolunteerGroup( + name: "Group 2", + createdAt: "2027-09-09", + volunteersRequired: 5, + ), + ]; + + when(mockEventService.fetchVolunteerGroupsByEvent("1")) + .thenAnswer((_) async => mockGroups); + + await tester.pumpWidget(volunteerGroupsScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(VolunteerGroupsScreen), findsOneWidget); + expect(find.text("There aren't any volunteer groups"), findsNothing); + expect(find.byKey(const Key("group_data")), findsNWidgets(2)); + + expect(find.byIcon(Icons.edit), findsNWidgets(2)); + + await tester.tap(find.byIcon(Icons.edit).first); + await tester.pumpAndSettle(); + }); + testWidgets("Check if no groups show up", (tester) async { + final mockEventService = locator(); + + when(mockEventService.fetchVolunteerGroupsByEvent("1")) + .thenAnswer((_) async => []); + + await tester.pumpWidget(volunteerGroupsScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(VolunteerGroupsScreen), findsOneWidget); + expect(find.text("There aren't any volunteer groups"), findsOneWidget); + }); + testWidgets("Check if add group method throw error show up", + (tester) async { + final mockEventService = locator(); + final mockGroups = [ + EventVolunteerGroup(name: "Group 1", createdAt: "2027-09-08"), + EventVolunteerGroup(name: "Group 2", createdAt: "2027-09-09"), + ]; + + when(mockEventService.fetchVolunteerGroupsByEvent("1")) + .thenAnswer((_) async => mockGroups); + + when( + mockEventService.createVolunteerGroup({ + 'eventId': "1", + 'name': "New Group", + 'volunteersRequired': 5, + }), + ).thenThrow("exception"); + + await tester.pumpWidget(volunteerGroupsScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(VolunteerGroupsScreen), findsOneWidget); + expect(find.text("There aren't any volunteer groups"), findsNothing); + expect(find.byKey(const Key("group_data")), findsNWidgets(2)); + + expect(find.byKey(const Key("add_group_btn")), findsOneWidget); + await tester.tap(find.byKey(const Key("add_group_btn"))); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key("add_grp_dialogue")), findsOneWidget); + expect(find.text("Cancel"), findsOneWidget); + + await tester.tap(find.text("Cancel")); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key("add_grp_dialogue")), findsNothing); + await tester.tap(find.byKey(const Key("add_group_btn"))); + await tester.pumpAndSettle(); + + await tester.enterText( + find.byKey(const Key("group_name_field")), + "New Group", + ); + await tester.enterText( + find.byKey(const Key("volunteers_required_field")), + "5", + ); + + await tester.tap(find.text('Create Group')); + await tester.pumpAndSettle(); + + expect( + find.text('Failed to create group'), + findsOneWidget, + ); + }); + testWidgets("Check if add group method throw entre data show up", + (tester) async { + final mockEventService = locator(); + final mockGroups = [ + EventVolunteerGroup(name: "Group 1", createdAt: "2027-09-08"), + EventVolunteerGroup(name: "Group 2", createdAt: "2027-09-09"), + ]; + + when(mockEventService.fetchVolunteerGroupsByEvent("1")) + .thenAnswer((_) async => mockGroups); + + when( + mockEventService.createVolunteerGroup({ + 'eventId': "1", + 'name': "New Group", + 'volunteersRequired': 5, + }), + ).thenThrow("exception"); + + await tester.pumpWidget(volunteerGroupsScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(VolunteerGroupsScreen), findsOneWidget); + expect(find.text("There aren't any volunteer groups"), findsNothing); + expect(find.byKey(const Key("group_data")), findsNWidgets(2)); + + expect(find.byKey(const Key("add_group_btn")), findsOneWidget); + await tester.tap(find.byKey(const Key("add_group_btn"))); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key("add_grp_dialogue")), findsOneWidget); + + await tester.enterText( + find.byKey(const Key("group_name_field")), + "", + ); + await tester.enterText( + find.byKey(const Key("volunteers_required_field")), + "0", + ); + + await tester.tap(find.text('Create Group')); + await tester.pumpAndSettle(); + + expect( + find.text('Please enter valid data'), + findsOneWidget, + ); + }); + }); +} diff --git a/test/widget_tests/after_auth_screens/events/edit_event_page_test.dart b/test/widget_tests/after_auth_screens/events/edit_event_page_test.dart index d8ea7078d..c76749397 100644 --- a/test/widget_tests/after_auth_screens/events/edit_event_page_test.dart +++ b/test/widget_tests/after_auth_screens/events/edit_event_page_test.dart @@ -72,7 +72,7 @@ Widget editEventScreen({ }, ); -void main() { +void main() async { setUpAll(() async { SizeConfig().test(); setupLocator(); diff --git a/test/widget_tests/after_auth_screens/events/event_info_page_test.dart b/test/widget_tests/after_auth_screens/events/event_info_page_test.dart index 054f21ce9..1843a6754 100644 --- a/test/widget_tests/after_auth_screens/events/event_info_page_test.dart +++ b/test/widget_tests/after_auth_screens/events/event_info_page_test.dart @@ -10,7 +10,9 @@ import 'package:talawa/router.dart' as router; import 'package:talawa/services/size_config.dart'; import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; +import 'package:talawa/views/after_auth_screens/events/event_info_body.dart'; import 'package:talawa/views/after_auth_screens/events/event_info_page.dart'; +import 'package:talawa/views/after_auth_screens/events/volunteer_groups_screen.dart'; import '../../../helpers/test_helpers.dart'; @@ -88,38 +90,66 @@ void main() { unregisterViewModels(); }); group('Test EventInfoPage', () { - // testWidgets('Test Share button', (tester) async { - // mockNetworkImages(() async { - // await tester.pumpWidget(createEventInfoPage(true, true)); - // await tester.pumpAndSettle(); + testWidgets('Test tab Bar appears', (tester) async { + mockNetworkImages(() async { + await tester.pumpWidget(createEventInfoPage(true, true)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key("tabBar")), findsOneWidget); + }); + }); - // final shareButton = find.byIcon(Icons.share); - // expect(shareButton, findsOneWidget); + testWidgets('Test event info section appears', (tester) async { + mockNetworkImages(() async { + await tester.pumpWidget(createEventInfoPage(true, true)); + await tester.pumpAndSettle(); + expect(find.byKey(const Key("tabBar")), findsOneWidget); + expect(find.text('Info'), findsOneWidget); + expect(find.byType(EventInfoBody), findsOneWidget); - // await tester.tap(shareButton); - // await tester.pumpAndSettle(); - // }); - // }); + //check if delete floating button appears when user is creator + expect(find.byIcon(Icons.delete), findsOneWidget); + await tester.tap(find.byIcon(Icons.delete)); + await tester.pumpAndSettle(); + }); + }); testWidgets('Test FloatingActionButton', (tester) async { mockNetworkImages(() async { - await tester.pumpWidget(createEventInfoPage(true, true)); + await tester.pumpWidget(createEventInfoPage(true, false)); await tester.pumpAndSettle(); - expect(find.byType(FloatingActionButton), findsOneWidget); + expect( + find.byKey( + const Key("registerEventFloatingbtn"), + ), + findsOneWidget, + ); - await tester.tap(find.byType(FloatingActionButton)); + await tester.tap( + find.byKey( + const Key("registerEventFloatingbtn"), + ), + ); + await tester.pumpAndSettle(); }); }); - - testWidgets('Test Delete FloatingActionButton', (tester) async { + testWidgets('Test if volunteer section appears on swipe left', + (tester) async { mockNetworkImages(() async { - await tester.pumpWidget(createEventInfoPage(true, false)); + await tester.pumpWidget(createEventInfoPage(true, true)); await tester.pumpAndSettle(); + expect(find.byKey(const Key("tabBar")), findsOneWidget); + expect(find.text('Info'), findsOneWidget); + expect(find.byType(VolunteerGroupsScreen), findsNothing); - expect(find.byType(FloatingActionButton), findsOneWidget); + await tester.drag( + find.byType(TabBarView), + const Offset(-500.0, 0.0), + ); + await tester.pumpAndSettle(); - await tester.tap(find.byType(FloatingActionButton)); + expect(find.byType(EventInfoBody), findsNothing); + expect(find.byType(VolunteerGroupsScreen), findsOneWidget); }); }); });