Skip to content

Commit

Permalink
Merge pull request #2476 from acterglobal/kumar/widget-testing
Browse files Browse the repository at this point in the history
Widget Testing Enhancement : PinListWidget and PinItemWidget
  • Loading branch information
gnunicorn authored Jan 5, 2025
2 parents e11211d + c6a54d1 commit b9826d9
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 0 deletions.
4 changes: 4 additions & 0 deletions app/lib/features/pins/widgets/pin_list_item_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import 'package:go_router/go_router.dart';
import 'package:skeletonizer/skeletonizer.dart';

class PinListItemWidget extends ConsumerWidget {

static const pinItemClick = Key('pin_item_click');

final String pinId;
final RefDetails? refDetails;
final bool showSpace;
Expand Down Expand Up @@ -48,6 +51,7 @@ class PinListItemWidget extends ConsumerWidget {
return Card(
margin: cardMargin,
child: ListTile(
key: pinItemClick,
onTap: () {
final pinId = pin.eventIdStr();
if (onTaPinItem == null) {
Expand Down
91 changes: 91 additions & 0 deletions app/test/features/pins/pin_item_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import 'package:acter/common/providers/room_providers.dart';
import 'package:acter/features/pins/providers/pins_provider.dart';
import 'package:acter/features/pins/widgets/pin_list_item_widget.dart';
import 'package:atlas_icons/atlas_icons.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import '../../helpers/mock_pins_providers.dart';
import '../../helpers/test_util.dart';

class MockOnTapPinItem extends Mock {
void call(String pinId);
}

void main() {
late FakeActerPin mockPin;
late MockOnTapPinItem mockOnTapPinItem;

setUp(() {
mockPin = FakeActerPin();
mockOnTapPinItem = MockOnTapPinItem();
});

Future<void> createWidgetUnderTest({
required WidgetTester tester,
bool isShowSpaceName = false,
bool isPinIndication = false,
Function(String)? onTapPinItem,
}) async {
final mockedNotifier = MockAsyncPinNotifier(shouldFail: false);
await tester.pumpProviderWidget(
overrides: [
pinProvider.overrideWith(() => mockedNotifier),
roomDisplayNameProvider.overrideWith((a, b) => 'Space Name'),
],
child: PinListItemWidget(
pinId: mockPin.eventId.toString(),
showSpace: isShowSpaceName,
onTaPinItem: onTapPinItem,
showPinIndication: isPinIndication,
),
);
// Wait for the async provider to load
await tester.pump();
}

testWidgets('displays pin title', (tester) async {
await createWidgetUnderTest(tester: tester);
expect(find.text('Pin Title'), findsOneWidget);
});

testWidgets('calls onTapPinItem callback when tapped', (tester) async {
await createWidgetUnderTest(
tester: tester,
onTapPinItem: (mockOnTapPinItem..call('1234')).call,
);
await tester.tap(find.byKey(PinListItemWidget.pinItemClick));
await tester.pump();

verify(() => mockOnTapPinItem.call('1234')).called(1);
});

testWidgets('displays space name when isShowSpaceName is true',
(tester) async {
await createWidgetUnderTest(tester: tester, isShowSpaceName: true);

expect(find.text('Space Name'), findsOneWidget);
});

testWidgets('Do not displays space name when isShowSpaceName is false',
(tester) async {
await createWidgetUnderTest(tester: tester, isShowSpaceName: false);

expect(find.text('Space Name'), findsNothing);
});

testWidgets('displays pin indication when isPinIndication is true',
(tester) async {
await createWidgetUnderTest(tester: tester, isPinIndication: true);

expect(find.text('Pin'), findsOneWidget);
expect(find.byIcon(Atlas.pin), findsOneWidget);
});

testWidgets('displays pin indication when isPinIndication is true',
(tester) async {
await createWidgetUnderTest(tester: tester, isPinIndication: false);

expect(find.text('Pin'), findsNothing);
expect(find.byIcon(Atlas.pin), findsNothing);
});
}
109 changes: 109 additions & 0 deletions app/test/features/pins/pin_list_widget_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import 'package:acter/common/providers/room_providers.dart';
import 'package:acter/features/bookmarks/providers/bookmarks_provider.dart';
import 'package:acter/features/pins/providers/pins_provider.dart';
import 'package:acter/features/pins/widgets/pin_list_item_widget.dart';
import 'package:acter/features/pins/widgets/pin_list_widget.dart';
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/error_helpers.dart';
import '../../helpers/mock_pins_providers.dart';
import '../../helpers/test_util.dart';

void main() {
late FakeActerPin mockPin;
setUp(() {
mockPin = FakeActerPin();
});

group('Pin List', () {
testWidgets('displays empty state when there are no pins', (tester) async {
//Arrange:
var emptyState = Text('empty state');
final provider = FutureProvider<List<ActerPin>>((ref) async => []);

// Build the widget tree with an empty provider
await tester.pumpProviderWidget(
child: PinListWidget(
pinListProvider: provider,
emptyState: emptyState,
),
);

// Act
await tester.pumpAndSettle(); // Allow the widget to settle

// Assert
expect(
find.text('empty state'),
findsOneWidget,
); // Ensure the empty state widget is displayed
});

testWidgets('displays error state when there is issue in loading pin list',
(tester) async {
bool shouldFail = true;

final provider = FutureProvider<List<ActerPin>>((ref) async {
if (shouldFail) {
shouldFail = false;
throw 'Some Error';
} else {
return [];
}
});

// Build the widget tree with the mocked provider
await tester.pumpProviderWidget(
child: PinListWidget(
pinListProvider: provider,
),
);
await tester.ensureErrorPageWithRetryWorks();
});
});

testWidgets('displays list of pins when data is available', (tester) async {
// Arrange
final mockedNotifier = SimplyRetuningAsyncPinListNotifier([mockPin]);
final pinNotifier = RetryMockAsyncPinNotifier();
final mockPin1 = FakeActerPin(pinTitle: 'Fake Pin1');
final mockPin2 = FakeActerPin(pinTitle: 'Fake Pin2');
final mockPin3 = FakeActerPin(pinTitle: 'Fake Pin3');

final finalListProvider = FutureProvider<List<ActerPin>>(
(ref) async => [
mockPin1,
mockPin2,
mockPin3,
],
);

// Build the widget tree with the mocked provider
await tester.pumpProviderWidget(
overrides: [
roomMembershipProvider.overrideWith((a, b) => null),
isBookmarkedProvider.overrideWith((a, b) => false),
roomDisplayNameProvider.overrideWith((a, b) => 'test'),
pinsProvider.overrideWith((ref, pin) => []),
pinListProvider.overrideWith(() => mockedNotifier),
pinProvider.overrideWith(() => pinNotifier),
],
child: PinListWidget(
pinListProvider: finalListProvider,
),
);
// Initial build
await tester.pump();

// Wait for async operations
await tester.pump(const Duration(seconds: 1));

expect(
find.byType(PinListItemWidget),
findsNWidgets(3),
reason: 'Should find 3 PinItem widgets',
);
});
}
19 changes: 19 additions & 0 deletions app/test/helpers/mock_pins_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mocktail/mocktail.dart';
import 'package:riverpod/riverpod.dart';

class MockAsyncPinNotifier
extends AutoDisposeFamilyAsyncNotifier<ActerPin, String>
with Mock
implements AsyncPinNotifier {
bool shouldFail;

MockAsyncPinNotifier({this.shouldFail = true});

@override
Future<ActerPin> build(String arg) async {
if (shouldFail) {
// toggle failure so the retry works
shouldFail = !shouldFail;
throw 'Expected fail: Space not loaded';
}
return FakeActerPin();
}
}

class SimplyRetuningAsyncPinListNotifier
extends FamilyAsyncNotifier<List<ActerPin>, String?>
with Mock
Expand Down

0 comments on commit b9826d9

Please sign in to comment.