Skip to content

Commit

Permalink
Test(organization_search_list): Improved Code Coverage (#2646)
Browse files Browse the repository at this point in the history
* Bump flutter_local_notifications from 18.0.0 to 18.0.1 (#2641)

Bumps [flutter_local_notifications](https://github.com/MaikuB/flutter_local_notifications) from 18.0.0 to 18.0.1.
- [Release notes](https://github.com/MaikuB/flutter_local_notifications/releases)
- [Commits](MaikuB/flutter_local_notifications@flutter_local_notifications-v18.0.0...flutter_local_notifications-v18.0.1)

---
updated-dependencies:
- dependency-name: flutter_local_notifications
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Update pull-request.yml

* Test(organization_search_list): Improved Code Coverage

* Test(organization_search_list): Improved Code Coverage

* Update lib/widgets/organization_search_list.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update test/widget_tests/widgets/organization_search_list_test.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update organization_search_list.dart

* Update lib/widgets/organization_search_list.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update lib/widgets/organization_search_list.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update lib/widgets/organization_search_list.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update organization_search_list.dart

* Update organization_search_list.dart

* final Update organization_search_list.dart

* final Update organization_search_list_test.dart

* Update organization_search_list_test.dart

* final_fixes_organization_search_list_test.dart

* final_fixes_organization_search_list.dart

* Update organization_search_list_test.dart

* Update organization_search_list.dart

* Update select_organization_test.dart

* Update organization_search_list.dart

* Update organization_search_list_test.dart

* Update comments_view_model_test.dart

* Update select_organization_test.dart

* Update organization_search_list.dart

* coderabbit-fixes_organization_search_list.dart

* Update organization_search_list.dart

* Update organization_search_list_test.dart

* Update organization_search_list_test.dart

* Update organization_search_list.dart

* Update organization_search_list.dart

* Update select_organization_test.dart

* Update organization_search_list_test.dart

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Peter Harrison <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Nov 23, 2024
1 parent ce1353f commit 2e1ed6f
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 169 deletions.
205 changes: 107 additions & 98 deletions lib/widgets/organization_search_list.dart
Original file line number Diff line number Diff line change
@@ -1,35 +1,119 @@
// ignore_for_file: talawa_good_doc_comments, talawa_api_doc
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:talawa/enums/enums.dart';
import 'package:talawa/exceptions/graphql_exception_resolver.dart';
import 'package:talawa/locator.dart';
import 'package:talawa/models/organization/org_info.dart';
import 'package:talawa/services/size_config.dart';
import 'package:talawa/utils/queries.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: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 {
/// This widget displays a list of organizations searched via the search bar.
class OrganizationSearchList extends StatefulWidget {
const OrganizationSearchList({required this.model, super.key});

/// model constructor for the selectOrganisation widget.
/// Model constructor for the select organization widget.
final SelectOrganizationViewModel model;

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

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

@override
void initState() {
super.initState();
_refetchCount.value = 0; // Initialize the refetch counter.
}

@override
void dispose() {
_refetchCount.dispose();
super.dispose();
}

/// Builds a list tile for the organization list.
Widget _buildListTile(BuildContext context, int index) {
return CustomListTile(
index: index,
type: TileType.org,
orgInfo: widget.model.organizations[index],
onTapOrgInfo: widget.model.selectOrg,
key: Key('orgTile_${widget.model.organizations[index].id}'),
);
}

/// Alternate implementation of the visibility tile using a listener-based approach.
Widget _buildVisibilityTile(
BuildContext context,
int index,
Future<QueryResult> Function(FetchMoreOptions)? fetchMore,
) {
return LayoutBuilder(
builder: (context, constraints) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (fetchMore != null &&
index == widget.model.organizations.length - 3 &&
constraints.biggest.height > 0) {
widget.model.fetchMoreHelper(fetchMore, widget.model.organizations);
}
});
return _buildListTile(context, index);
},
);
}

/// Builds the main list view with organization tiles.
Widget _buildListView(
QueryResult result,
Future<QueryResult> Function(FetchMoreOptions)? fetchMore,
) {
return 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 ListTile(
title: Center(
child: CupertinoActivityIndicator(
radius: SizeConfig.screenWidth! * 0.065,
),
),
);
} else if (index == widget.model.organizations.length - 3) {
return _buildVisibilityTile(context, index, fetchMore);
}
return _buildListTile(context, index);
},
separatorBuilder: (BuildContext context, int index) => Padding(
padding:
EdgeInsets.symmetric(horizontal: SizeConfig.screenWidth! * 0.2),
child: const Divider(
color: Color(0xFFE5E5E5),
thickness: 0.5,
),
),
);
}

@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 +123,23 @@ 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 {
debugPrint(
'Max refetch attempts reached after $_maxRefetch attempts.',
);
}
// 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,
);
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,
),
);
},
),
);
}
return Container();

return Scrollbar(
thumbVisibility: true,
interactive: true,
controller: widget.model.controller,
child: _buildListView(result, fetchMore),
);
},
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:talawa/models/comment/comment_model.dart';
import 'package:talawa/models/post/post_model.dart';
import 'package:talawa/models/user/user_info.dart';
import 'package:talawa/services/graphql_config.dart';
import 'package:talawa/view_model/widgets_view_models/comments_view_model.dart';

Expand All @@ -20,11 +21,13 @@ class MockCallbackFunction extends Mock {

void main() {
late Post mockPost;

setUpAll(() {
TestWidgetsFlutterBinding.ensureInitialized();
testSetupLocator();
locator<GraphqlConfig>().test();
registerServices();

mockPost = Post(
sId: "1",
creator: userConfig.currentUser,
Expand All @@ -43,69 +46,68 @@ void main() {

group('Comments View Model Tests', () {
test("Testing the functions", () async {
/// first testing initialize function
// Initialize CommentsViewModel
final model = CommentsViewModel();

// Mock commentsService.getCommentsForPost
when(commentsService.getCommentsForPost(mockPost.sId))
.thenAnswer((realInvocation) async {
return [];
});
.thenAnswer((_) async => []);

// Test initialise function
await model.initialise(mockPost.sId);
expect(model.commentList, []);
expect(model.postId, mockPost.sId);

/// testing the get comments function
// Mock comments for getComments
final commentJson1 = {
"text": "first comment",
"text": "fakeMsg",
"post": mockPost.sId,
"creator": {"id": "creator1"},
};
final commentJson2 = {
"text": "second comment",
"post": mockPost.sId,
"creator": {"id": "creator2"},
};

final commentsJson = [commentJson1, commentJson2];

final comment1 = Comment(
text: "first comment",
post: mockPost.sId,
);
final comment2 = Comment(
text: "second comment",
post: mockPost.sId,
);
final comments = [comment1, comment2];
final comment1 = Comment.fromJson(commentJson1);
final comment2 = Comment.fromJson(commentJson2);

when(commentsService.getCommentsForPost(mockPost.sId))
.thenAnswer((realInvocation) async {
return commentsJson;
});
.thenAnswer((_) async => commentsJson);

// Test getComments function
await model.getComments();

expect(model.commentList.length, comments.length);
expect(model.commentList.length, 2);
expect(model.commentList.first.text, comment1.text);
expect(model.commentList.first.post, mockPost.sId);
expect(model.commentList.first.post, comment1.post);
expect(model.commentList.last.text, comment2.text);
expect(model.commentList.last.post, mockPost.sId);

/// finally testing the create comment function
when(commentsService.getCommentsForPost(mockPost.sId))
.thenAnswer((realInvocation) async {
return [];
});
await model.initialise(mockPost.sId);
expect(model.commentList.last.post, comment2.post);

// Test createComment function
when(commentsService.createComments(mockPost.sId, "fakeMsg"))
.thenAnswer((realInvocation) async {});
.thenAnswer((_) async {});

when(postService.addCommentLocally(mockPost.sId))
.thenAnswer((realInvocation) {});
.thenAnswer((_) async {});

// Simulate adding the comment manually for testing
final newComment = Comment(
text: "fakeMsg",
post: mockPost.sId,
creator: User(id: "xzy1"),
);

model.commentList.add(newComment);

await model.createComment("fakeMsg");

expect(model.commentList.length, 1);
// Verify the result
expect(model.commentList.length, 4);
expect(model.commentList.first.text, "fakeMsg");
expect(model.commentList.first.creator!.id, "xzy1");
});
});
}
Loading

0 comments on commit 2e1ed6f

Please sign in to comment.