Files
sharedinbox/test/unit/email_repository_cancel_change_test.dart
T
Thomas Güttler 43e1744614 feat: implement optimized Undo for delete and move actions
- Added UndoService with 10-action history stack.
- Integrated Undo Snackbar into EmailListScreen and EmailDetailScreen.
- Added EmailRepository.cancelPendingChange to optimize undo by removing
  unsynced local mutations.
- Fixed sorting bug in compareMailboxes for unknown roles.
- Increased unit coverage to 83% with new model and utility tests.
- Verified with full test suite (task check).
2026-05-08 11:14:54 +02:00

110 lines
3.2 KiB
Dart

import 'package:drift/drift.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mockito/annotations.dart';
import 'package:sharedinbox/core/storage/secure_storage.dart';
import 'package:sharedinbox/data/db/database.dart';
import 'package:sharedinbox/data/repositories/account_repository_impl.dart';
import 'package:sharedinbox/data/repositories/email_repository_impl.dart';
import 'db_test_helper.dart';
import 'email_repository_cancel_change_test.mocks.dart';
@GenerateMocks([http.Client, SecureStorage])
void main() {
late AppDatabase db;
late EmailRepositoryImpl repo;
late MockClient mockHttpClient;
late MockSecureStorage mockStorage;
setUpAll(() {
configureSqliteForTests();
});
setUp(() async {
db = openTestDatabase();
mockHttpClient = MockClient();
mockStorage = MockSecureStorage();
final accounts = AccountRepositoryImpl(db, mockStorage);
repo = EmailRepositoryImpl(
db,
accounts,
httpClient: mockHttpClient,
);
});
tearDown(() async {
await db.close();
});
test('cancelPendingChange removes an unattempted change', () async {
await db.into(db.pendingChanges).insert(
PendingChangesCompanion.insert(
accountId: 'acc1',
resourceType: 'Email',
resourceId: 'e1',
changeType: 'move',
payload: '{}',
createdAt: DateTime.now(),
),
);
final cancelled = await repo.cancelPendingChange('e1', 'move');
expect(cancelled, isTrue);
final remaining = await db.select(db.pendingChanges).get();
expect(remaining, isEmpty);
});
test('cancelPendingChange does not remove attempted changes', () async {
await db.into(db.pendingChanges).insert(
PendingChangesCompanion.insert(
accountId: 'acc1',
resourceType: 'Email',
resourceId: 'e1',
changeType: 'move',
payload: '{}',
createdAt: DateTime.now(),
attempts: const Value(1),
),
);
final cancelled = await repo.cancelPendingChange('e1', 'move');
expect(cancelled, isFalse);
final remaining = await db.select(db.pendingChanges).get();
expect(remaining, hasLength(1));
});
test('cancelPendingChange only removes the latest matching change', () async {
final now = DateTime.now();
await db.into(db.pendingChanges).insert(
PendingChangesCompanion.insert(
accountId: 'acc1',
resourceType: 'Email',
resourceId: 'e1',
changeType: 'move',
payload: '{"id": 1}',
createdAt: now,
),
);
await db.into(db.pendingChanges).insert(
PendingChangesCompanion.insert(
accountId: 'acc1',
resourceType: 'Email',
resourceId: 'e1',
changeType: 'move',
payload: '{"id": 2}',
createdAt: now.add(const Duration(seconds: 1)),
),
);
final cancelled = await repo.cancelPendingChange('e1', 'move');
expect(cancelled, isTrue);
final remaining = await db.select(db.pendingChanges).get();
expect(remaining, hasLength(1));
expect(remaining.first.payload, '{"id": 1}');
});
}