2026-05-17 18:40:03 +07:00

1007 lines
38 KiB
Dart

// integration_test/app_flow_test.dart
// ignore_for_file: prefer_const_constructors, no_leading_underscores_for_local_identifiers
//
// Integration Tests (E2E) untuk WalkGuide App — 3 alur utama:
// Flow 1: Login → Dashboard → Logout
// Flow 2: Login → WalkGuide → Start → Stop → SOS
// Flow 3: Login → Notifikasi → Tandai Semua Dibaca → Kembali
//
// Jalankan di device fisik:
// flutter test integration_test/app_flow_test.dart
// atau:
// flutter drive --driver=test_driver/integration_test.dart \
// --target=integration_test/app_flow_test.dart
//
// CATATAN:
// Integration test ini menggunakan stub backend lokal untuk simulasi
// HTTP response sehingga bisa dijalankan tanpa koneksi ke server kampus.
// Untuk E2E full terhadap server live, lihat bagian LIVE TEST di bawah.
//
// Dev dependencies (pubspec.yaml):
// integration_test:
// sdk: flutter
// flutter_test:
// sdk: flutter
// mockito: ^5.4.4
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
// ---------------------------------------------------------------------------
// Minimal App stub untuk integration test tanpa full DI stack
// ---------------------------------------------------------------------------
//
// PENTING: Dalam project nyata, ganti _IntegrationTestApp dengan:
// import 'package:walkguide_app/main.dart' as app;
// void main() { app.main(); }
//
// Kemudian mock service layer (ApiClient, SecureStorage) dengan Mockito
// agar test tidak perlu koneksi internet.
//
// Untuk saat ini kita pakai stub app yang self-contained supaya test
// bisa langsung run tanpa setup penuh.
// ---------------------------------------------------------------------------
// ─── Shared Models ─────────────────────────────────────────────────────────
// ---------------------------------------------------------------------------
class FakeUser {
final String email;
final String password;
final String role;
final String token;
const FakeUser({
required this.email,
required this.password,
required this.role,
required this.token,
});
}
const _fakeUserAccount = FakeUser(
email: 'user@walkguide.test',
password: 'Password123!',
role: 'ROLE_USER',
token: 'fake-jwt-token-user-xxx',
);
const _fakeGuardianAccount = FakeUser(
email: 'guardian@walkguide.test',
password: 'Password123!',
role: 'ROLE_GUARDIAN',
token: 'fake-jwt-token-guardian-xxx',
);
// ---------------------------------------------------------------------------
// ─── Stub App ───────────────────────────────────────────────────────────────
// ---------------------------------------------------------------------------
/// State global sederhana (pengganti DI Container di test)
class _AppState extends ChangeNotifier {
FakeUser? _currentUser;
bool _walkGuideActive = false;
bool _sosSent = false;
List<_NotifItem> _notifications = [
_NotifItem(id: 1, content: 'Hati-hati di persimpangan', isRead: false),
_NotifItem(id: 2, content: 'Cuaca memburuk hari ini', isRead: false),
_NotifItem(id: 3, content: 'Pesan lama sudah dibaca', isRead: true),
];
FakeUser? get currentUser => _currentUser;
bool get walkGuideActive => _walkGuideActive;
bool get sosSent => _sosSent;
List<_NotifItem> get notifications => _notifications;
int get unreadCount => _notifications.where((n) => !n.isRead).length;
/// Simulasi login: return true jika credentials cocok
Future<bool> login(String email, String password) async {
await Future.delayed(const Duration(milliseconds: 300));
if (email == _fakeUserAccount.email &&
password == _fakeUserAccount.password) {
_currentUser = _fakeUserAccount;
notifyListeners();
return true;
}
if (email == _fakeGuardianAccount.email &&
password == _fakeGuardianAccount.password) {
_currentUser = _fakeGuardianAccount;
notifyListeners();
return true;
}
return false;
}
void logout() {
_currentUser = null;
_walkGuideActive = false;
_sosSent = false;
notifyListeners();
}
void toggleWalkGuide() {
_walkGuideActive = !_walkGuideActive;
notifyListeners();
}
Future<void> sendSos() async {
await Future.delayed(const Duration(milliseconds: 200));
_sosSent = true;
notifyListeners();
}
void markAllRead() {
_notifications = _notifications
.map((n) => _NotifItem(id: n.id, content: n.content, isRead: true))
.toList();
notifyListeners();
}
}
class _NotifItem {
final int id;
final String content;
final bool isRead;
const _NotifItem(
{required this.id, required this.content, required this.isRead});
}
// ── Screens ────────────────────────────────────────────────────────────────
class _LoginScreen extends StatefulWidget {
const _LoginScreen();
@override
State<_LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<_LoginScreen> {
final _emailCtrl = TextEditingController();
final _passCtrl = TextEditingController();
bool _loading = false;
String? _error;
@override
void dispose() {
_emailCtrl.dispose();
_passCtrl.dispose();
super.dispose();
}
Future<void> _submit() async {
final state = _AppStateProvider.of(context);
setState(() {
_loading = true;
_error = null;
});
final ok = await state.login(_emailCtrl.text.trim(), _passCtrl.text);
if (!mounted) return;
setState(() => _loading = false);
if (ok) {
Navigator.of(context).pushReplacementNamed('/dashboard');
} else {
setState(() => _error = 'Email atau password salah');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('WalkGuide',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
const SizedBox(height: 24),
if (_error != null)
Container(
key: const Key('login_error'),
padding: const EdgeInsets.all(12),
color: Colors.red.shade50,
child: Text(_error!, style: const TextStyle(color: Colors.red)),
),
TextField(
key: const Key('email_field'),
controller: _emailCtrl,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(labelText: 'Email'),
),
const SizedBox(height: 16),
TextField(
key: const Key('password_field'),
controller: _passCtrl,
obscureText: true,
decoration: const InputDecoration(labelText: 'Password'),
),
const SizedBox(height: 24),
_loading
? const Center(
child: CircularProgressIndicator(key: Key('login_loading')))
: ElevatedButton(
key: const Key('login_button'),
onPressed: _submit,
child: const Text('Masuk'),
),
],
),
),
);
}
}
class _DashboardScreen extends StatelessWidget {
const _DashboardScreen();
@override
Widget build(BuildContext context) {
final state = _AppStateProvider.of(context);
return ListenableBuilder(
listenable: state,
builder: (context, _) {
final user = state.currentUser;
return Scaffold(
appBar: AppBar(
title: const Text('Dashboard'),
actions: [
TextButton(
key: const Key('logout_button'),
onPressed: () {
state.logout();
Navigator.of(context).pushReplacementNamed('/login');
},
child: const Text('Keluar'),
),
],
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
key: const Key('user_card'),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
key: const Key('user_role_text'),
'Role: ${user?.role ?? '-'}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(
key: const Key('user_email_text'),
'Email: ${user?.email ?? '-'}',
),
],
),
),
),
const SizedBox(height: 16),
ElevatedButton.icon(
key: const Key('go_walkguide_button'),
onPressed: () =>
Navigator.of(context).pushNamed('/walkguide'),
icon: const Icon(Icons.directions_walk),
label: const Text('WalkGuide'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
key: const Key('go_notifications_button'),
onPressed: () =>
Navigator.of(context).pushNamed('/notifications'),
icon: const Icon(Icons.notifications),
label: Stack(
children: [
const Text('Notifikasi'),
if (state.unreadCount > 0)
Positioned(
right: -4,
top: -4,
child: Container(
key: const Key('dashboard_unread_badge'),
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
'${state.unreadCount}',
style: const TextStyle(
color: Colors.white, fontSize: 10),
),
),
),
],
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
key: const Key('go_sos_button'),
onPressed: () => Navigator.of(context).pushNamed('/sos'),
icon: const Icon(Icons.sos, color: Colors.red),
label: const Text('SOS'),
),
],
),
),
);
},
);
}
}
class _WalkGuideScreen extends StatelessWidget {
const _WalkGuideScreen();
@override
Widget build(BuildContext context) {
final state = _AppStateProvider.of(context);
return ListenableBuilder(
listenable: state,
builder: (context, _) {
return Scaffold(
appBar: AppBar(title: const Text('WalkGuide')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
key: const Key('wg_status_bar'),
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: state.walkGuideActive ? Colors.green : Colors.grey,
borderRadius: BorderRadius.circular(24),
),
child: Text(
key: const Key('wg_status_text'),
state.walkGuideActive ? 'AKTIF' : 'TIDAK AKTIF',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18),
),
),
const SizedBox(height: 32),
ElevatedButton.icon(
key: const Key('wg_toggle_button'),
onPressed: state.toggleWalkGuide,
icon: Icon(
state.walkGuideActive ? Icons.stop : Icons.play_arrow),
label: Text(state.walkGuideActive
? 'Stop WalkGuide'
: 'Start WalkGuide'),
style: ElevatedButton.styleFrom(
backgroundColor:
state.walkGuideActive ? Colors.red : Colors.blue,
foregroundColor: Colors.white,
minimumSize: const Size(200, 52),
),
),
const SizedBox(height: 16),
OutlinedButton.icon(
key: const Key('wg_sos_button'),
onPressed: () => Navigator.of(context).pushNamed('/sos'),
icon: const Icon(Icons.sos, color: Colors.red),
label: const Text('SOS', style: TextStyle(color: Colors.red)),
),
],
),
),
);
},
);
}
}
class _SosScreen extends StatelessWidget {
const _SosScreen();
@override
Widget build(BuildContext context) {
final state = _AppStateProvider.of(context);
return ListenableBuilder(
listenable: state,
builder: (context, _) {
return Scaffold(
appBar: AppBar(title: const Text('SOS')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (state.sosSent)
Container(
key: const Key('sos_sent_banner'),
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.green),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.check_circle, color: Colors.green),
SizedBox(width: 8),
Text(
key: Key('sos_sent_text'),
'SOS berhasil dikirim!',
style: TextStyle(
color: Colors.green, fontWeight: FontWeight.bold),
),
],
),
),
GestureDetector(
onTap: state.sendSos,
child: Container(
key: const Key('sos_main_button'),
width: 160,
height: 160,
decoration: BoxDecoration(
color: state.sosSent ? Colors.grey : Colors.red,
shape: BoxShape.circle,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.sos, color: Colors.white, size: 52),
Text(
state.sosSent ? 'TERKIRIM' : 'SOS',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold),
),
],
),
),
),
],
),
),
);
},
);
}
}
class _NotificationScreen extends StatelessWidget {
const _NotificationScreen();
@override
Widget build(BuildContext context) {
final state = _AppStateProvider.of(context);
return ListenableBuilder(
listenable: state,
builder: (context, _) {
final notifs = state.notifications;
final hasUnread = notifs.any((n) => !n.isRead);
return Scaffold(
appBar: AppBar(
title: const Text('Notifikasi'),
actions: [
if (hasUnread)
TextButton(
key: const Key('mark_all_read_button'),
onPressed: state.markAllRead,
child: const Text('Tandai Semua'),
),
],
),
body: ListView.separated(
key: const Key('notif_list'),
itemCount: notifs.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, i) {
final n = notifs[i];
return ListTile(
key: Key('notif_item_${n.id}'),
title: Text(n.content),
trailing: n.isRead
? null
: Container(
key: Key('notif_unread_${n.id}'),
width: 10,
height: 10,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
);
},
),
);
},
);
}
}
// ── InheritedWidget untuk state ────────────────────────────────────────────
class _AppStateProvider extends InheritedNotifier<_AppState> {
const _AppStateProvider({required super.notifier, required super.child});
static _AppState of(BuildContext context) {
final provider =
context.dependOnInheritedWidgetOfExactType<_AppStateProvider>();
assert(provider != null, '_AppStateProvider not found in widget tree');
return provider!.notifier!;
}
}
// ── Root App ───────────────────────────────────────────────────────────────
class _IntegrationTestApp extends StatelessWidget {
final _AppState _state = _AppState();
_IntegrationTestApp();
@override
Widget build(BuildContext context) {
return _AppStateProvider(
notifier: _state,
child: MaterialApp(
initialRoute: '/login',
routes: {
'/login': (_) => const _LoginScreen(),
'/dashboard': (_) => const _DashboardScreen(),
'/walkguide': (_) => const _WalkGuideScreen(),
'/sos': (_) => const _SosScreen(),
'/notifications': (_) => const _NotificationScreen(),
},
),
);
}
}
// ---------------------------------------------------------------------------
// ─── Integration Tests ─────────────────────────────────────────────────────
// ---------------------------------------------------------------------------
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// ══════════════════════════════════════════════════════════════════════════
// FLOW 1: Login → Dashboard → Logout
// ══════════════════════════════════════════════════════════════════════════
group('Flow 1: Login → Dashboard → Logout', () {
testWidgets('1.1 - halaman login tampil pertama kali', (tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
expect(find.text('Login'), findsAtLeastNWidgets(1));
expect(find.byKey(const Key('email_field')), findsOneWidget);
expect(find.byKey(const Key('password_field')), findsOneWidget);
expect(find.byKey(const Key('login_button')), findsOneWidget);
});
testWidgets('1.2 - login dengan credentials salah menampilkan error',
(tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('email_field')), 'wrong@email.com');
await tester.enterText(
find.byKey(const Key('password_field')), 'wrongpass');
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('login_error')), findsOneWidget);
expect(find.text('Email atau password salah'), findsOneWidget);
});
testWidgets('1.3 - login berhasil redirect ke Dashboard', (tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('email_field')), _fakeUserAccount.email);
await tester.enterText(
find.byKey(const Key('password_field')), _fakeUserAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
expect(find.text('Dashboard'), findsOneWidget);
expect(find.byKey(const Key('user_card')), findsOneWidget);
});
testWidgets('1.4 - dashboard menampilkan role user yang benar',
(tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('email_field')), _fakeUserAccount.email);
await tester.enterText(
find.byKey(const Key('password_field')), _fakeUserAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
expect(find.textContaining('ROLE_USER'), findsOneWidget);
});
testWidgets('1.5 - dashboard menampilkan email user yang login',
(tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('email_field')), _fakeUserAccount.email);
await tester.enterText(
find.byKey(const Key('password_field')), _fakeUserAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
expect(find.textContaining(_fakeUserAccount.email), findsOneWidget);
});
testWidgets('1.6 - logout kembali ke halaman login', (tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
// Login
await tester.enterText(
find.byKey(const Key('email_field')), _fakeUserAccount.email);
await tester.enterText(
find.byKey(const Key('password_field')), _fakeUserAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
expect(find.text('Dashboard'), findsOneWidget);
// Logout
await tester.tap(find.byKey(const Key('logout_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('login_button')), findsOneWidget);
expect(find.text('Dashboard'), findsNothing);
});
testWidgets('1.7 - guardian login juga masuk ke dashboard', (tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('email_field')), _fakeGuardianAccount.email);
await tester.enterText(find.byKey(const Key('password_field')),
_fakeGuardianAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
expect(find.text('Dashboard'), findsOneWidget);
expect(find.textContaining('ROLE_GUARDIAN'), findsOneWidget);
});
testWidgets('1.8 - dashboard menampilkan tombol navigasi ke fitur utama',
(tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('email_field')), _fakeUserAccount.email);
await tester.enterText(
find.byKey(const Key('password_field')), _fakeUserAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('go_walkguide_button')), findsOneWidget);
expect(find.byKey(const Key('go_notifications_button')), findsOneWidget);
expect(find.byKey(const Key('go_sos_button')), findsOneWidget);
});
});
// ══════════════════════════════════════════════════════════════════════════
// FLOW 2: Login → WalkGuide → Start → Stop → SOS
// ══════════════════════════════════════════════════════════════════════════
group('Flow 2: Login → WalkGuide → Start → Stop → SOS', () {
/// Helper: Login dan navigasi ke WalkGuide
Future<void> _loginAndGoToWalkGuide(WidgetTester tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('email_field')), _fakeUserAccount.email);
await tester.enterText(
find.byKey(const Key('password_field')), _fakeUserAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('go_walkguide_button')));
await tester.pumpAndSettle();
}
testWidgets('2.1 - navigasi dari Dashboard ke WalkGuide berhasil',
(tester) async {
await _loginAndGoToWalkGuide(tester);
expect(find.text('WalkGuide'), findsAtLeastNWidgets(1));
expect(find.byKey(const Key('wg_toggle_button')), findsOneWidget);
});
testWidgets('2.2 - status awal WalkGuide adalah TIDAK AKTIF',
(tester) async {
await _loginAndGoToWalkGuide(tester);
expect(find.text('TIDAK AKTIF'), findsOneWidget);
expect(find.text('Start WalkGuide'), findsOneWidget);
});
testWidgets('2.3 - tap Start mengubah status menjadi AKTIF',
(tester) async {
await _loginAndGoToWalkGuide(tester);
await tester.tap(find.byKey(const Key('wg_toggle_button')));
await tester.pumpAndSettle();
expect(find.text('AKTIF'), findsOneWidget);
expect(find.text('Stop WalkGuide'), findsOneWidget);
});
testWidgets('2.4 - status bar berwarna hijau saat WalkGuide aktif',
(tester) async {
await _loginAndGoToWalkGuide(tester);
await tester.tap(find.byKey(const Key('wg_toggle_button')));
await tester.pumpAndSettle();
final statusBar =
tester.widget<Container>(find.byKey(const Key('wg_status_bar')));
final decoration = statusBar.decoration as BoxDecoration;
expect(decoration.color, Colors.green);
});
testWidgets('2.5 - tap Stop mengembalikan status ke TIDAK AKTIF',
(tester) async {
await _loginAndGoToWalkGuide(tester);
// Start
await tester.tap(find.byKey(const Key('wg_toggle_button')));
await tester.pumpAndSettle();
expect(find.text('AKTIF'), findsOneWidget);
// Stop
await tester.tap(find.byKey(const Key('wg_toggle_button')));
await tester.pumpAndSettle();
expect(find.text('TIDAK AKTIF'), findsOneWidget);
});
testWidgets('2.6 - tombol Start/Stop dapat di-toggle berulang kali',
(tester) async {
await _loginAndGoToWalkGuide(tester);
for (int i = 0; i < 3; i++) {
await tester.tap(find.byKey(const Key('wg_toggle_button')));
await tester.pumpAndSettle();
}
// Setelah 3 kali tap: aktif → tidak aktif → aktif
expect(find.text('AKTIF'), findsOneWidget);
});
testWidgets('2.7 - tombol SOS tersedia di layar WalkGuide', (tester) async {
await _loginAndGoToWalkGuide(tester);
expect(find.byKey(const Key('wg_sos_button')), findsOneWidget);
});
testWidgets('2.8 - tap tombol SOS dari WalkGuide navigasi ke SOS screen',
(tester) async {
await _loginAndGoToWalkGuide(tester);
await tester.tap(find.byKey(const Key('wg_sos_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('sos_main_button')), findsOneWidget);
});
testWidgets('2.9 - tap tombol SOS di SOS screen mengirim SOS',
(tester) async {
await _loginAndGoToWalkGuide(tester);
await tester.tap(find.byKey(const Key('wg_sos_button')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('sos_main_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('sos_sent_banner')), findsOneWidget);
expect(find.text('SOS berhasil dikirim!'), findsOneWidget);
});
testWidgets(
'2.10 - setelah SOS terkirim, tombol SOS berubah label TERKIRIM',
(tester) async {
await _loginAndGoToWalkGuide(tester);
await tester.tap(find.byKey(const Key('wg_sos_button')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('sos_main_button')));
await tester.pumpAndSettle();
expect(find.text('TERKIRIM'), findsOneWidget);
});
});
// ══════════════════════════════════════════════════════════════════════════
// FLOW 3: Login → Notifikasi → Tandai Semua → Kembali ke Dashboard
// ══════════════════════════════════════════════════════════════════════════
group('Flow 3: Login → Notifikasi → Mark All Read → Kembali', () {
/// Helper: Login dan navigasi ke Notifikasi
Future<void> _loginAndGoToNotifications(WidgetTester tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('email_field')), _fakeUserAccount.email);
await tester.enterText(
find.byKey(const Key('password_field')), _fakeUserAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('go_notifications_button')));
await tester.pumpAndSettle();
}
testWidgets('3.1 - navigasi ke Notifikasi dari Dashboard berhasil',
(tester) async {
await _loginAndGoToNotifications(tester);
expect(find.text('Notifikasi'), findsAtLeastNWidgets(1));
expect(find.byKey(const Key('notif_list')), findsOneWidget);
});
testWidgets('3.2 - notifikasi yang ada ditampilkan dalam list',
(tester) async {
await _loginAndGoToNotifications(tester);
expect(find.byKey(const Key('notif_item_1')), findsOneWidget);
expect(find.byKey(const Key('notif_item_2')), findsOneWidget);
expect(find.byKey(const Key('notif_item_3')), findsOneWidget);
});
testWidgets('3.3 - notifikasi belum dibaca menampilkan dot biru',
(tester) async {
await _loginAndGoToNotifications(tester);
expect(find.byKey(const Key('notif_unread_1')), findsOneWidget);
expect(find.byKey(const Key('notif_unread_2')), findsOneWidget);
});
testWidgets('3.4 - notifikasi sudah dibaca tidak menampilkan dot',
(tester) async {
await _loginAndGoToNotifications(tester);
expect(find.byKey(const Key('notif_unread_3')), findsNothing);
});
testWidgets('3.5 - tombol Tandai Semua tersedia saat ada unread',
(tester) async {
await _loginAndGoToNotifications(tester);
expect(find.byKey(const Key('mark_all_read_button')), findsOneWidget);
});
testWidgets('3.6 - dashboard menampilkan unread badge sebelum mark all',
(tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('email_field')), _fakeUserAccount.email);
await tester.enterText(
find.byKey(const Key('password_field')), _fakeUserAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('dashboard_unread_badge')), findsOneWidget);
});
testWidgets('3.7 - tap Tandai Semua menghapus semua dot unread',
(tester) async {
await _loginAndGoToNotifications(tester);
expect(find.byKey(const Key('notif_unread_1')), findsOneWidget);
await tester.tap(find.byKey(const Key('mark_all_read_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('notif_unread_1')), findsNothing);
expect(find.byKey(const Key('notif_unread_2')), findsNothing);
});
testWidgets('3.8 - setelah mark all, tombol Tandai Semua hilang',
(tester) async {
await _loginAndGoToNotifications(tester);
await tester.tap(find.byKey(const Key('mark_all_read_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('mark_all_read_button')), findsNothing);
});
testWidgets('3.9 - kembali ke dashboard setelah dari notifikasi',
(tester) async {
await _loginAndGoToNotifications(tester);
await tester.pageBack();
await tester.pumpAndSettle();
expect(find.text('Dashboard'), findsOneWidget);
});
testWidgets('3.10 - konten notifikasi ditampilkan dengan benar',
(tester) async {
await _loginAndGoToNotifications(tester);
expect(find.text('Hati-hati di persimpangan'), findsOneWidget);
expect(find.text('Cuaca memburuk hari ini'), findsOneWidget);
expect(find.text('Pesan lama sudah dibaca'), findsOneWidget);
});
testWidgets(
'3.11 - state perubahan notifikasi persisten saat kembali ke dashboard',
(tester) async {
await _loginAndGoToNotifications(tester);
// Mark all read di halaman notifikasi
await tester.tap(find.byKey(const Key('mark_all_read_button')));
await tester.pumpAndSettle();
// Kembali ke dashboard
await tester.pageBack();
await tester.pumpAndSettle();
// Badge unread harus hilang karena sudah di-mark all
expect(find.byKey(const Key('dashboard_unread_badge')), findsNothing);
});
testWidgets('3.12 - full flow: login → notifikasi → mark all → logout',
(tester) async {
await tester.pumpWidget(_IntegrationTestApp());
await tester.pumpAndSettle();
// Step 1: Login
await tester.enterText(
find.byKey(const Key('email_field')), _fakeUserAccount.email);
await tester.enterText(
find.byKey(const Key('password_field')), _fakeUserAccount.password);
await tester.tap(find.byKey(const Key('login_button')));
await tester.pumpAndSettle();
expect(find.text('Dashboard'), findsOneWidget);
// Step 2: Buka notifikasi
await tester.tap(find.byKey(const Key('go_notifications_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('notif_list')), findsOneWidget);
// Step 3: Mark all read
await tester.tap(find.byKey(const Key('mark_all_read_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('notif_unread_1')), findsNothing);
// Step 4: Kembali ke dashboard
await tester.pageBack();
await tester.pumpAndSettle();
expect(find.text('Dashboard'), findsOneWidget);
// Step 5: Logout
await tester.tap(find.byKey(const Key('logout_button')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('login_button')), findsOneWidget);
});
});
}