Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test(organization_search_list): Improved Code Coverage #2646

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e979086
Bump flutter_local_notifications from 18.0.0 to 18.0.1 (#2641)
dependabot[bot] Nov 14, 2024
14e21b2
Update pull-request.yml
palisadoes Nov 14, 2024
747dfb4
Test(organization_search_list): Improved Code Coverage
ARYPROGRAMMER Nov 16, 2024
99b019d
Test(organization_search_list): Improved Code Coverage
ARYPROGRAMMER Nov 16, 2024
1049f41
Update lib/widgets/organization_search_list.dart
ARYPROGRAMMER Nov 16, 2024
3aefff1
Update test/widget_tests/widgets/organization_search_list_test.dart
ARYPROGRAMMER Nov 16, 2024
392dc2c
Update organization_search_list.dart
ARYPROGRAMMER Nov 16, 2024
9ade34f
Update lib/widgets/organization_search_list.dart
ARYPROGRAMMER Nov 16, 2024
6a7a4fd
Update lib/widgets/organization_search_list.dart
ARYPROGRAMMER Nov 16, 2024
dc971df
Update lib/widgets/organization_search_list.dart
ARYPROGRAMMER Nov 16, 2024
3eb3733
Update organization_search_list.dart
ARYPROGRAMMER Nov 16, 2024
0c4d407
Update organization_search_list.dart
ARYPROGRAMMER Nov 16, 2024
2d2d477
final Update organization_search_list.dart
ARYPROGRAMMER Nov 16, 2024
190ac47
final Update organization_search_list_test.dart
ARYPROGRAMMER Nov 16, 2024
9b60c2e
Update organization_search_list_test.dart
ARYPROGRAMMER Nov 16, 2024
a06f571
final_fixes_organization_search_list_test.dart
ARYPROGRAMMER Nov 16, 2024
e370074
final_fixes_organization_search_list.dart
ARYPROGRAMMER Nov 16, 2024
6b111dc
Update organization_search_list_test.dart
ARYPROGRAMMER Nov 16, 2024
4991003
Merge branch 'develop-postgres' into organization_search_list
ARYPROGRAMMER Nov 16, 2024
817f16b
Update organization_search_list.dart
ARYPROGRAMMER Nov 17, 2024
ba8bfe3
Update select_organization_test.dart
ARYPROGRAMMER Nov 17, 2024
54f63d5
Update organization_search_list.dart
ARYPROGRAMMER Nov 17, 2024
af0059d
Update organization_search_list_test.dart
ARYPROGRAMMER Nov 17, 2024
f2618b6
Update comments_view_model_test.dart
ARYPROGRAMMER Nov 18, 2024
321b652
Update select_organization_test.dart
ARYPROGRAMMER Nov 18, 2024
b6f5056
Update organization_search_list.dart
ARYPROGRAMMER Nov 18, 2024
6e2cc0b
coderabbit-fixes_organization_search_list.dart
ARYPROGRAMMER Nov 18, 2024
3c911a3
Merge branch 'develop-postgres' into organization_search_list
ARYPROGRAMMER Nov 20, 2024
87e0b70
Update organization_search_list.dart
ARYPROGRAMMER Nov 20, 2024
1c5a504
Update organization_search_list_test.dart
ARYPROGRAMMER Nov 20, 2024
c5211e8
Update organization_search_list_test.dart
ARYPROGRAMMER Nov 20, 2024
83f871e
Update organization_search_list.dart
ARYPROGRAMMER Nov 20, 2024
4ebf370
Update organization_search_list.dart
ARYPROGRAMMER Nov 21, 2024
3a37561
Update select_organization_test.dart
ARYPROGRAMMER Nov 21, 2024
cfe9e1d
Update organization_search_list_test.dart
ARYPROGRAMMER Nov 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ jobs:
name: "Base branch check"
runs-on: ubuntu-latest
steps:
- name: "Check if base branch is develop"
if: github.event.pull_request.base.ref != 'develop'
- name: "Check if base branch is develop-postgres"
if: github.event.pull_request.base.ref != 'develop-postgres'
run: |
echo "PR is not against develop branch. Please refer PR_GUIDELINES.md"
exit 1
Expand Down
175 changes: 82 additions & 93 deletions lib/widgets/organization_search_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,28 @@ import 'package:talawa/view_model/pre_auth_view_models/select_organization_view_
import 'package:talawa/widgets/custom_list_tile.dart';
import 'package:visibility_detector/visibility_detector.dart';

/// This class returns the widget that shows all the matching orgs searched in the search bar.
class OrganizationSearchList extends StatelessWidget {
class OrganizationSearchList extends StatefulWidget {
const OrganizationSearchList({required this.model, super.key});

/// model constructor for the selectOrganisation widget.
final SelectOrganizationViewModel model;

@override
_OrganizationSearchListState createState() => _OrganizationSearchListState();
}

class _OrganizationSearchListState extends State<OrganizationSearchList> {
static const int maxRefetch = 10;
final ValueNotifier<int> _refetchCount = ValueNotifier<int>(0);

ARYPROGRAMMER marked this conversation as resolved.
Show resolved Hide resolved
ARYPROGRAMMER marked this conversation as resolved.
Show resolved Hide resolved
@override
Widget build(BuildContext context) {
int noOfRefetch = 0;
const int maxRefetch = 10;
return GraphQLProvider(
client: ValueNotifier<GraphQLClient>(graphqlConfig.authClient()),
child: Query(
options: QueryOptions(
document: gql(Queries().fetchJoinInOrgByName),
variables: {
'nameStartsWith': model.searchController.text,
// fetch 30 items only, will fetch more when scrolling index is at the 3rd last item!
'nameStartsWith': widget.model.searchController.text,
'first': 30,
'skip': 0,
},
Expand All @@ -39,98 +42,84 @@ class OrganizationSearchList extends StatelessWidget {
Future<QueryResult> Function(FetchMoreOptions)? fetchMore,
Future<QueryResult?> Function()? refetch,
}) {
// checking for any errors, if true fetch again!
if (result.hasException) {
final isException =
GraphqlExceptionResolver.encounteredExceptionOrError(
result.exception!,
);
print(isException);
if (noOfRefetch <= maxRefetch) {
noOfRefetch++;
refetch!();
}
} else {
// If the result is still loading!
if (!result.isLoading) {
model.organizations = OrgInfo().fromJsonToList(
result.data!['organizationsConnection'],
if (_refetchCount.value < maxRefetch) {
_refetchCount.value++;
refetch?.call();
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Failed to load organizations. Please try again later.'),
),
);
}
// return the Scroll bar widget for scrolling down the organizations.
return Scrollbar(
thumbVisibility: true,
interactive: true,
controller: model.controller,
// Listview is a scrollable list of widgets arranged linearly.
child: ListView.separated(
controller: model.controller,
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: result.isLoading
? model.organizations.length + 1
: model.organizations.length,
itemBuilder: (BuildContext context, int index) {
// If the index is at the end of the list!
if (index == model.organizations.length) {
// return the ListTile showing the loading icon!
return ListTile(
title: Center(
child: CupertinoActivityIndicator(
radius: SizeConfig.screenWidth! * 0.065,
),
),
);
}
// If the index is at the 3rd last item in the organization list.
if (index == model.organizations.length - 3) {
// return VisibilityDetector and fetch more items in the list to show up!
return VisibilityDetector(
key: const Key('OrgSelItem'),
onVisibilityChanged: (VisibilityInfo info) {
if (info.visibleFraction > 0) {
print(model.organizations.length);
model.fetchMoreHelper(
fetchMore!,
model.organizations,
ARYPROGRAMMER marked this conversation as resolved.
Show resolved Hide resolved
);
print(model.organizations.length);
}
},
child: CustomListTile(
index: index,
type: TileType.org,
orgInfo: model.organizations[index],
onTapOrgInfo: (item) => model.selectOrg(item),
key: Key('OrgSelItem$index'),
),
);
}
// return CustomeTile that shows a particular item in the list!
return CustomListTile(
index: index,
type: TileType.org,
orgInfo: model.organizations[index],
onTapOrgInfo: (item) => model.selectOrg(item),
key: Key('OrgSelItem$index'),
);
},
separatorBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.only(
left: SizeConfig.screenWidth! * 0.2,
right: 12,
),
child: const Divider(
color: Color(0xFFE5E5E5),
thickness: 0.5,
),
);
},
} else if (!result.isLoading) {
final data = result.data;
if (data != null && data['organizationsConnection'] != null) {
try {
widget.model.organizations = OrgInfo().fromJsonToList(
data['organizationsConnection'],
);
} catch (e) {
debugPrint('Error parsing organization data: $e');
widget.model.organizations = [];
}
}
}

return _buildListView(context, result, fetchMore);
},
),
);
}

Widget _buildListView(BuildContext context, QueryResult result,
Future<QueryResult> Function(FetchMoreOptions)? fetchMore) {
return Scrollbar(
thumbVisibility: true,
interactive: true,
controller: widget.model.controller,
child: ListView.separated(
controller: widget.model.controller,
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: result.isLoading
? widget.model.organizations.length + 1
: widget.model.organizations.length,
itemBuilder: (BuildContext context, int index) {
if (index == widget.model.organizations.length) {
return const ListTile(
title: Center(
child: CupertinoActivityIndicator(),
),
);
}
return Container();

return VisibilityDetector(
key: Key('OrgSelItem$index'),
ARYPROGRAMMER marked this conversation as resolved.
Show resolved Hide resolved
onVisibilityChanged: (VisibilityInfo info) {
if (info.visibleFraction > 0 &&
index == widget.model.organizations.length - 3) {
if (fetchMore != null) {
widget.model
.fetchMoreHelper(fetchMore, widget.model.organizations);
}
}
ARYPROGRAMMER marked this conversation as resolved.
Show resolved Hide resolved
},
child: CustomListTile(
index: index,
type: TileType.org,
orgInfo: widget.model.organizations[index],
onTapOrgInfo: widget.model.selectOrg,
key: Key('orgTile_${widget.model.organizations[index].id}'),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider(
thickness: 0.5,
);
},
),
);
Expand Down
4 changes: 2 additions & 2 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,10 @@ packages:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "725145682706fb0e5a30f93e5cb64f3df7ed7743de749bd555b22bf75ee718c0"
sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610
url: "https://pub.dev"
source: hosted
version: "18.0.0"
version: "18.0.1"
flutter_local_notifications_linux:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ dependencies:
sdk: flutter
flutter_braintree: ^4.0.0
flutter_cache_manager: ^3.4.1
flutter_local_notifications: ^18.0.0
flutter_local_notifications: ^18.0.1
flutter_localizations:
sdk: flutter
flutter_reaction_button: ^3.0.0+3
Expand Down
113 changes: 113 additions & 0 deletions test/widget_tests/widgets/organization_search_list_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import 'package:flutter/cupertino.dart';
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/organization/org_info.dart';
import 'package:talawa/view_model/pre_auth_view_models/select_organization_view_model.dart';
import 'package:talawa/widgets/custom_list_tile.dart';
import 'package:talawa/widgets/organization_search_list.dart';

import '../../helpers/test_locator.dart';

class MockSelectOrganizationViewModel extends Mock
implements SelectOrganizationViewModel {}

class MockGraphQLClient extends Mock implements GraphQLClient {}

void main() {
late MockSelectOrganizationViewModel mockModel;
late MockGraphQLClient mockGraphQLClient;

setUp(() {
mockModel = MockSelectOrganizationViewModel();
mockGraphQLClient = MockGraphQLClient();
});

Widget createWidgetUnderTest() {
return MaterialApp(
home: Scaffold(
body: OrganizationSearchList(model: mockModel),
),
);
}

testWidgets('OrganizationSearchList renders correctly',
(WidgetTester tester) async {
when(mockModel.searchController.text).thenReturn('');
await tester.pumpWidget(createWidgetUnderTest());
expect(find.byType(Query), findsOneWidget);
});

testWidgets('Displays loading indicator when fetching data',
(WidgetTester tester) async {
when(mockModel.searchController.text).thenReturn('test');
await tester.pumpWidget(createWidgetUnderTest());

// Simulate loading state
await tester.pump();
expect(find.byType(CupertinoActivityIndicator), findsWidgets);
});

testWidgets('Displays CustomListTile for each organization item',
(WidgetTester tester) async {
final organizations = [
OrgInfo(name: 'Org1', id: '1'),
OrgInfo(name: 'Org2', id: '2'),
];

when(mockModel.searchController.text).thenReturn('org');
when(mockModel.organizations).thenReturn(organizations);

await tester.pumpWidget(createWidgetUnderTest());
await tester.pump();

expect(find.byType(CustomListTile), findsNWidgets(2));
});

testWidgets('Shows error message and attempts refetch on exception',
(WidgetTester tester) async {
when(mockModel.searchController.text).thenReturn('test');
when(mockGraphQLClient.query(
QueryOptions(
document: gql(queries.getPluginsList()),
),
)).thenAnswer((_) async {
throw Exception('GraphQL error');
});

await tester.pumpWidget(createWidgetUnderTest());
await tester.pump();

// Expect error handling and retry logic here
expect(find.textContaining('GraphQL error'), findsNothing);
});

ARYPROGRAMMER marked this conversation as resolved.
Show resolved Hide resolved
testWidgets('Calls fetchMoreHelper when near end of list',
(WidgetTester tester) async {
final organizations = List<OrgInfo>.generate(
30, (index) => OrgInfo(name: 'Org $index', id: '$index'));

when(mockModel.searchController.text).thenReturn('org');
when(mockModel.organizations).thenReturn(organizations);
when(mockModel.hasMoreItems).thenReturn(true);

await tester.pumpWidget(createWidgetUnderTest());

await tester.scrollUntilVisible(
find.byType(PageView).first,
500.0,
);
final fetchMore = (FetchMoreOptions options) =>
Future.value(QueryResult.internal(source: QueryResultSource.network));
verify(mockModel.fetchMoreHelper(fetchMore!, organizations)).called(1);

// Verify no more calls when hasMoreItems is false
when(mockModel.hasMoreItems).thenReturn(false);
await tester.scrollUntilVisible(
find.byType(PageView).last,
500.0,
);
verifyNever(mockModel.fetchMoreHelper(any, any));
});
}