Files
pfm-ocr/test/camera_drawer_logout_test.dart
T
fhanyuh caf8e98378 chore: normalize line endings (CRLF -> LF)
No content changes: git diff --ignore-all-space over these files is empty.
The churn came from editing on Windows against a repo checked out with LF.
2026-08-27 10:40:49 +07:00

172 lines
5.5 KiB
Dart

import 'package:hive/hive.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:app_pfm_ocr_v2/core/storage/local_storage.dart';
import 'package:app_pfm_ocr_v2/features/camera/camera_drawer.dart';
import 'package:app_pfm_ocr_v2/main.dart';
import 'package:app_pfm_ocr_v2/models/document_model.dart';
class FakeBox extends Fake implements Box {
@override
Future<int> clear() async => 0;
}
class MockLocalStorage extends LocalStorage {
final List<Map<String, dynamic>> pending;
MockLocalStorage({this.pending = const []});
@override
Future<void> init() async {}
@override
Box get documentBox => FakeBox();
@override
Box get pendingDocumentsBox => FakeBox();
@override
List<DocumentModel> getAllDocuments() => [];
@override
List<Map<String, dynamic>> getAllPendingDocuments() => pending;
@override
Future<void> savePendingDocument(Map<String, dynamic> data) async {}
@override
Future<void> removePendingDocument(String id) async {}
}
Map<String, dynamic> _syncFailedPendingDoc(String id) {
return {
'id': id,
'image_path': 'fake_$id.jpg',
'status': 'syncFailed',
'document': null,
'latitude': null,
'longitude': null,
'error_message': 'Gagal sinkronisasi ke server: 500 Internal Server Error',
'created_at': DateTime.now().toIso8601String(),
'scan_mode': 'DO',
};
}
Widget _buildTestApp(LocalStorage storage) {
final router = GoRouter(
initialLocation: '/camera',
routes: [
GoRoute(
path: '/camera',
builder: (context, state) => Scaffold(
appBar: AppBar(),
drawer: const CameraDrawer(),
body: Builder(
builder: (context) => Center(
child: ElevatedButton(
onPressed: () => Scaffold.of(context).openDrawer(),
child: const Text('Open Drawer'),
),
),
),
),
),
GoRoute(path: '/login', builder: (context, state) => const Scaffold(body: Text('LOGIN_SCREEN'))),
GoRoute(path: '/documents', builder: (context, state) => const Scaffold(body: Text('DOCS_SCREEN'))),
],
);
return ProviderScope(
overrides: [localStorageProvider.overrideWithValue(storage)],
child: MaterialApp.router(routerConfig: router),
);
}
/// CameraDrawer's mode-toggle row (DO Scan / Product Scan buttons) has a
/// pre-existing RenderFlex overflow at the fonts/metrics flutter_test falls
/// back to (unrelated to logout behavior - out of scope for this task).
/// `TestWidgetsFlutterBinding.runTest` installs its own `FlutterError.onError`
/// around the test body, so the override must happen inside the body itself
/// (a `setUp` override gets clobbered) to suppress just that known overflow
/// while still surfacing anything else.
Future<void> _ignoringKnownOverflow(Future<void> Function() body) async {
final original = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails details) {
if (details.exception.toString().contains('A RenderFlex overflowed')) return;
original?.call(details);
};
try {
await body();
} finally {
FlutterError.onError = original;
}
}
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
testWidgets('logs out immediately when there are no pending documents', (tester) async {
await _ignoringKnownOverflow(() async {
await tester.pumpWidget(_buildTestApp(MockLocalStorage()));
await tester.pumpAndSettle();
await tester.tap(find.text('Open Drawer'));
await tester.pumpAndSettle();
await tester.tap(find.text('Logout'));
await tester.pumpAndSettle();
expect(find.text('LOGIN_SCREEN'), findsOneWidget);
expect(find.text('Ada Dokumen Belum Tersinkron'), findsNothing);
});
});
testWidgets('warns and stays put when logout is cancelled with pending documents', (tester) async {
await _ignoringKnownOverflow(() async {
final storage = MockLocalStorage(pending: [_syncFailedPendingDoc('1')]);
await tester.pumpWidget(_buildTestApp(storage));
await tester.pumpAndSettle();
await tester.tap(find.text('Open Drawer'));
await tester.pumpAndSettle();
await tester.tap(find.text('Logout'));
await tester.pump();
expect(find.text('Ada Dokumen Belum Tersinkron'), findsOneWidget);
await tester.tap(find.text('Batal'));
await tester.pumpAndSettle();
expect(find.text('LOGIN_SCREEN'), findsNothing);
});
});
testWidgets('logs out after confirming the warning with pending documents', (tester) async {
await _ignoringKnownOverflow(() async {
final storage = MockLocalStorage(pending: [_syncFailedPendingDoc('1'), _syncFailedPendingDoc('2')]);
await tester.pumpWidget(_buildTestApp(storage));
await tester.pumpAndSettle();
await tester.tap(find.text('Open Drawer'));
await tester.pumpAndSettle();
await tester.tap(find.text('Logout'));
await tester.pump();
expect(find.text('Ada Dokumen Belum Tersinkron'), findsOneWidget);
expect(find.textContaining('2 dokumen'), findsOneWidget);
await tester.tap(find.text('Ya, Logout'));
await tester.pumpAndSettle();
expect(find.text('LOGIN_SCREEN'), findsOneWidget);
});
});
}