Compare commits
2
Commits
main
...
issue-286-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e344c57cd6 | ||
|
|
04a5ec4b23 |
@@ -109,3 +109,51 @@ jobs:
|
|||||||
- name: Cleanup TLS credentials
|
- name: Cleanup TLS credentials
|
||||||
if: always()
|
if: always()
|
||||||
run: rm -rf /tmp/dagger-tls /tmp/stunnel-dagger.conf /tmp/stunnel.pid
|
run: rm -rf /tmp/dagger-tls /tmp/stunnel-dagger.conf /tmp/stunnel.pid
|
||||||
|
|
||||||
|
merge-renovate:
|
||||||
|
name: Auto-merge Renovate PR
|
||||||
|
needs: [check]
|
||||||
|
if: github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Merge if automerge label is set
|
||||||
|
env:
|
||||||
|
FORGEJO_TOKEN: ${{ github.token }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
run: |
|
||||||
|
python3 - << 'PYEOF'
|
||||||
|
import os, json, urllib.request, urllib.error, sys
|
||||||
|
|
||||||
|
token = os.environ["FORGEJO_TOKEN"]
|
||||||
|
url_base = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
|
||||||
|
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
||||||
|
pr_number = os.environ["PR_NUMBER"]
|
||||||
|
api = f"{url_base}/api/v1/repos/{repo}"
|
||||||
|
headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
|
||||||
|
|
||||||
|
req = urllib.request.Request(f"{api}/issues/{pr_number}/labels", headers=headers)
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
labels = [l["name"] for l in json.loads(r.read())]
|
||||||
|
|
||||||
|
if "automerge" not in labels:
|
||||||
|
print(f"PR #{pr_number}: no 'automerge' label — major update, skipping")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
body = json.dumps({"Do": "merge"}).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{api}/pulls/{pr_number}/merge",
|
||||||
|
data=body, headers=headers, method="POST"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
print(f"PR #{pr_number} merged successfully")
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
err = e.read().decode()
|
||||||
|
if "already been merged" in err or "has been merged" in err:
|
||||||
|
print(f"PR #{pr_number} already merged — OK")
|
||||||
|
else:
|
||||||
|
print(f"Merge failed: {err}")
|
||||||
|
sys.exit(1)
|
||||||
|
PYEOF
|
||||||
|
|||||||
@@ -11,4 +11,13 @@ abstract class MailboxRepository {
|
|||||||
|
|
||||||
/// Deletes all locally-cached mailbox rows for [accountId].
|
/// Deletes all locally-cached mailbox rows for [accountId].
|
||||||
Future<void> clearForResync(String accountId);
|
Future<void> clearForResync(String accountId);
|
||||||
|
|
||||||
|
/// Creates a new mailbox named [name] for [accountId] and tags it with
|
||||||
|
/// [role] in the local database. For JMAP accounts the role is also sent
|
||||||
|
/// to the server. Returns the newly created [Mailbox].
|
||||||
|
Future<Mailbox> createMailboxWithRole(
|
||||||
|
String accountId,
|
||||||
|
String name,
|
||||||
|
String role,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,14 @@ class MailboxRepositoryImpl implements MailboxRepository {
|
|||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
final mailboxes = await client.listMailboxes(recursive: true);
|
final mailboxes = await client.listMailboxes(recursive: true);
|
||||||
|
|
||||||
|
// Pre-load existing DB roles so we can preserve manually-set roles for
|
||||||
|
// folders the server doesn't tag with a special-use attribute.
|
||||||
|
final existingRows = await (_db.select(_db.mailboxes)
|
||||||
|
..where((t) => t.accountId.equals(account.id)))
|
||||||
|
.get();
|
||||||
|
final existingRoles = {for (final r in existingRows) r.id: r.role};
|
||||||
|
|
||||||
for (final mb in mailboxes) {
|
for (final mb in mailboxes) {
|
||||||
final path = mb.path;
|
final path = mb.path;
|
||||||
final id = '${account.id}:$path';
|
final id = '${account.id}:$path';
|
||||||
@@ -96,6 +104,12 @@ class MailboxRepositoryImpl implements MailboxRepository {
|
|||||||
log('STATUS skipped for $path: $e');
|
log('STATUS skipped for $path: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the server-assigned role when available; fall back to the
|
||||||
|
// existing DB role so that manually-created folders (e.g. a user
|
||||||
|
// who just created their Archive folder) keep their role across syncs
|
||||||
|
// when the IMAP server does not expose a special-use attribute.
|
||||||
|
final role = _imapRole(mb) ?? existingRoles[id];
|
||||||
|
|
||||||
await _db.into(_db.mailboxes).insertOnConflictUpdate(
|
await _db.into(_db.mailboxes).insertOnConflictUpdate(
|
||||||
MailboxesCompanion.insert(
|
MailboxesCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
@@ -104,7 +118,7 @@ class MailboxRepositoryImpl implements MailboxRepository {
|
|||||||
name: mb.name,
|
name: mb.name,
|
||||||
unreadCount: Value(unread),
|
unreadCount: Value(unread),
|
||||||
totalCount: Value(total),
|
totalCount: Value(total),
|
||||||
role: Value(_imapRole(mb)),
|
role: Value(role),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -310,4 +324,104 @@ class MailboxRepositoryImpl implements MailboxRepository {
|
|||||||
..where((t) => t.accountId.equals(accountId)))
|
..where((t) => t.accountId.equals(accountId)))
|
||||||
.go();
|
.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<model.Mailbox> createMailboxWithRole(
|
||||||
|
String accountId,
|
||||||
|
String name,
|
||||||
|
String role,
|
||||||
|
) async {
|
||||||
|
final account = (await _accounts.getAccount(accountId))!;
|
||||||
|
final password = await _accounts.getPassword(accountId);
|
||||||
|
switch (account.type) {
|
||||||
|
case account_model.AccountType.imap:
|
||||||
|
return _createMailboxWithRoleImap(account, password, name, role);
|
||||||
|
case account_model.AccountType.jmap:
|
||||||
|
return _createMailboxWithRoleJmap(account, password, name, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<model.Mailbox> _createMailboxWithRoleImap(
|
||||||
|
account_model.Account account,
|
||||||
|
String password,
|
||||||
|
String name,
|
||||||
|
String role,
|
||||||
|
) async {
|
||||||
|
final client = await _imapConnect(
|
||||||
|
account,
|
||||||
|
_effectiveUsername(account),
|
||||||
|
password,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await client.createMailbox(name);
|
||||||
|
} finally {
|
||||||
|
await client.logout();
|
||||||
|
}
|
||||||
|
final id = '${account.id}:$name';
|
||||||
|
await _db.into(_db.mailboxes).insertOnConflictUpdate(
|
||||||
|
MailboxesCompanion.insert(
|
||||||
|
id: id,
|
||||||
|
accountId: account.id,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
role: Value(role),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final row = await (_db.select(_db.mailboxes)..where((t) => t.id.equals(id)))
|
||||||
|
.getSingle();
|
||||||
|
return _toModel(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<model.Mailbox> _createMailboxWithRoleJmap(
|
||||||
|
account_model.Account account,
|
||||||
|
String password,
|
||||||
|
String name,
|
||||||
|
String role,
|
||||||
|
) async {
|
||||||
|
final jmapUrl = account.jmapUrl;
|
||||||
|
if (jmapUrl == null || jmapUrl.isEmpty) {
|
||||||
|
throw Exception('JMAP account ${account.id} has no jmapUrl');
|
||||||
|
}
|
||||||
|
final jmap = await JmapClient.connect(
|
||||||
|
httpClient: _httpClient,
|
||||||
|
jmapUrl: Uri.parse(jmapUrl),
|
||||||
|
username: _effectiveUsername(account),
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
final responses = await jmap.call([
|
||||||
|
[
|
||||||
|
'Mailbox/set',
|
||||||
|
{
|
||||||
|
'accountId': jmap.accountId,
|
||||||
|
'create': {
|
||||||
|
'new-mailbox': {'name': name, 'role': role},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'0',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
final result = _responseArgs(responses, 0, 'Mailbox/set');
|
||||||
|
final created = result['created'] as Map<String, dynamic>?;
|
||||||
|
final newId =
|
||||||
|
(created?['new-mailbox'] as Map<String, dynamic>?)?['id'] as String?;
|
||||||
|
if (newId == null) {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to create mailbox "$name": server returned no ID',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final dbId = '${account.id}:$newId';
|
||||||
|
await _db.into(_db.mailboxes).insertOnConflictUpdate(
|
||||||
|
MailboxesCompanion.insert(
|
||||||
|
id: dbId,
|
||||||
|
accountId: account.id,
|
||||||
|
path: newId,
|
||||||
|
name: name,
|
||||||
|
role: Value(role),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final row = await (_db.select(_db.mailboxes)
|
||||||
|
..where((t) => t.id.equals(dbId)))
|
||||||
|
.getSingle();
|
||||||
|
return _toModel(row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:intl/intl.dart';
|
|||||||
|
|
||||||
import 'package:sharedinbox/core/models/account.dart';
|
import 'package:sharedinbox/core/models/account.dart';
|
||||||
import 'package:sharedinbox/core/models/email.dart';
|
import 'package:sharedinbox/core/models/email.dart';
|
||||||
|
import 'package:sharedinbox/core/models/mailbox.dart';
|
||||||
import 'package:sharedinbox/core/models/undo_action.dart';
|
import 'package:sharedinbox/core/models/undo_action.dart';
|
||||||
import 'package:sharedinbox/core/repositories/email_repository.dart';
|
import 'package:sharedinbox/core/repositories/email_repository.dart';
|
||||||
import 'package:sharedinbox/di.dart';
|
import 'package:sharedinbox/di.dart';
|
||||||
@@ -24,6 +25,8 @@ int _dayKey(DateTime dt) => dt.year * 10000 + dt.month * 100 + dt.day;
|
|||||||
String _fmtDate(DateTime dt) =>
|
String _fmtDate(DateTime dt) =>
|
||||||
_formattedDates[_dayKey(dt)] ??= _dateFmt.format(dt);
|
_formattedDates[_dayKey(dt)] ??= _dateFmt.format(dt);
|
||||||
|
|
||||||
|
enum _MissingFolderChoice { chooseExisting, createNew }
|
||||||
|
|
||||||
class EmailListScreen extends ConsumerStatefulWidget {
|
class EmailListScreen extends ConsumerStatefulWidget {
|
||||||
const EmailListScreen({
|
const EmailListScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -420,24 +423,79 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _batchMoveToRole(String role, String notFoundMessage) async {
|
Future<void> _batchMoveToRole(
|
||||||
|
String role, {
|
||||||
|
required String dialogTitle,
|
||||||
|
required String createFolderName,
|
||||||
|
}) async {
|
||||||
final ids = _selectedEmailIds;
|
final ids = _selectedEmailIds;
|
||||||
_clearSelection();
|
_clearSelection();
|
||||||
final mailbox = await ref
|
|
||||||
.read(mailboxRepositoryProvider)
|
final mailboxRepo = ref.read(mailboxRepositoryProvider);
|
||||||
.findMailboxByRole(widget.accountId, role);
|
Mailbox? mailbox =
|
||||||
|
await mailboxRepo.findMailboxByRole(widget.accountId, role);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
if (mailbox == null) {
|
if (mailbox == null) {
|
||||||
ScaffoldMessenger.of(
|
final choice = await showDialog<_MissingFolderChoice>(
|
||||||
context,
|
context: context,
|
||||||
).showSnackBar(
|
builder: (ctx) => AlertDialog(
|
||||||
SnackBar(
|
title: Text(dialogTitle),
|
||||||
duration: const Duration(seconds: 5),
|
actions: [
|
||||||
content: Text(notFoundMessage),
|
TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
Navigator.pop(ctx, _MissingFolderChoice.chooseExisting),
|
||||||
|
child: const Text('Choose existing folder'),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () =>
|
||||||
|
Navigator.pop(ctx, _MissingFolderChoice.createNew),
|
||||||
|
child: Text('Create "$createFolderName"'),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
if (!mounted || choice == null) return;
|
||||||
|
|
||||||
|
switch (choice) {
|
||||||
|
case _MissingFolderChoice.chooseExisting:
|
||||||
|
final mailboxes =
|
||||||
|
await mailboxRepo.observeMailboxes(widget.accountId).first;
|
||||||
|
if (!mounted) return;
|
||||||
|
final chosen = await showModalBottomSheet<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: [
|
||||||
|
const ListTile(
|
||||||
|
title: Text(
|
||||||
|
'Move to…',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
for (final m
|
||||||
|
in mailboxes.where((m) => m.path != widget.mailboxPath))
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.folder_outlined),
|
||||||
|
title: Text(m.name),
|
||||||
|
onTap: () => Navigator.pop(ctx, m.path),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (chosen == null || !mounted) return;
|
||||||
|
mailbox = mailboxes.firstWhere((m) => m.path == chosen);
|
||||||
|
case _MissingFolderChoice.createNew:
|
||||||
|
mailbox = await mailboxRepo.createMailboxWithRole(
|
||||||
|
widget.accountId,
|
||||||
|
createFolderName,
|
||||||
|
role,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final repo = ref.read(emailRepositoryProvider);
|
final repo = ref.read(emailRepositoryProvider);
|
||||||
|
|
||||||
// Fetch full email data before moving so we can restore them if user clicks Undo.
|
// Fetch full email data before moving so we can restore them if user clicks Undo.
|
||||||
@@ -463,8 +521,11 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
|
|||||||
unawaited(ref.read(undoServiceProvider.notifier).pushAction(action));
|
unawaited(ref.read(undoServiceProvider.notifier).pushAction(action));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _batchArchive() =>
|
Future<void> _batchArchive() => _batchMoveToRole(
|
||||||
_batchMoveToRole('archive', 'No archive folder found');
|
'archive',
|
||||||
|
dialogTitle: 'No archive folder found',
|
||||||
|
createFolderName: 'Archive',
|
||||||
|
);
|
||||||
|
|
||||||
Future<void> _refreshSearchAndPopIfEmpty() async {
|
Future<void> _refreshSearchAndPopIfEmpty() async {
|
||||||
if (!mounted || !_searching) return;
|
if (!mounted || !_searching) return;
|
||||||
@@ -543,8 +604,11 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _batchMarkSpam() =>
|
Future<void> _batchMarkSpam() => _batchMoveToRole(
|
||||||
_batchMoveToRole('junk', 'No spam folder found');
|
'junk',
|
||||||
|
dialogTitle: 'No spam folder found',
|
||||||
|
createFolderName: 'Junk',
|
||||||
|
);
|
||||||
|
|
||||||
Future<void> _batchMove() async {
|
Future<void> _batchMove() async {
|
||||||
final ids = _selectedEmailIds;
|
final ids = _selectedEmailIds;
|
||||||
|
|||||||
+7
-1
@@ -6,5 +6,11 @@
|
|||||||
"labels": ["dependencies"],
|
"labels": ["dependencies"],
|
||||||
"github-actions": {
|
"github-actions": {
|
||||||
"fileMatch": ["^\\.forgejo/workflows/[^/]+\\.ya?ml$"]
|
"fileMatch": ["^\\.forgejo/workflows/[^/]+\\.ya?ml$"]
|
||||||
}
|
},
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": ["minor", "patch", "pin", "digest", "lockFileMaintenance"],
|
||||||
|
"addLabels": ["automerge"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,22 @@ class _FakeMailboxes implements MailboxRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> clearForResync(String accountId) async {}
|
Future<void> clearForResync(String accountId) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailboxWithRole(
|
||||||
|
String accountId,
|
||||||
|
String name,
|
||||||
|
String role,
|
||||||
|
) async =>
|
||||||
|
Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
role: role,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FakeEmails implements EmailRepository {
|
class _FakeEmails implements EmailRepository {
|
||||||
|
|||||||
@@ -224,6 +224,21 @@ class FakeMailboxRepositoryWithInbox implements MailboxRepository {
|
|||||||
Future<Mailbox?> findMailboxByRole(String id, String role) async => null;
|
Future<Mailbox?> findMailboxByRole(String id, String role) async => null;
|
||||||
@override
|
@override
|
||||||
Future<void> clearForResync(String accountId) async {}
|
Future<void> clearForResync(String accountId) async {}
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailboxWithRole(
|
||||||
|
String accountId,
|
||||||
|
String name,
|
||||||
|
String role,
|
||||||
|
) async =>
|
||||||
|
Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
role: role,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AccountRepositoryWithMissingPlugin implements AccountRepository {
|
class _AccountRepositoryWithMissingPlugin implements AccountRepository {
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
// Do not manually edit this file.
|
// Do not manually edit this file.
|
||||||
|
|
||||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
import 'dart:async' as _i4;
|
import 'dart:async' as _i5;
|
||||||
|
|
||||||
import 'package:mockito/mockito.dart' as _i1;
|
import 'package:mockito/mockito.dart' as _i1;
|
||||||
import 'package:mockito/src/dummies.dart' as _i6;
|
import 'package:mockito/src/dummies.dart' as _i7;
|
||||||
import 'package:sharedinbox/core/models/account.dart' as _i5;
|
import 'package:sharedinbox/core/models/account.dart' as _i6;
|
||||||
import 'package:sharedinbox/core/models/email.dart' as _i2;
|
import 'package:sharedinbox/core/models/email.dart' as _i3;
|
||||||
import 'package:sharedinbox/core/models/mailbox.dart' as _i8;
|
import 'package:sharedinbox/core/models/mailbox.dart' as _i2;
|
||||||
import 'package:sharedinbox/core/repositories/account_repository.dart' as _i3;
|
import 'package:sharedinbox/core/repositories/account_repository.dart' as _i4;
|
||||||
import 'package:sharedinbox/core/repositories/email_repository.dart' as _i9;
|
import 'package:sharedinbox/core/repositories/email_repository.dart' as _i9;
|
||||||
import 'package:sharedinbox/core/repositories/mailbox_repository.dart' as _i7;
|
import 'package:sharedinbox/core/repositories/mailbox_repository.dart' as _i8;
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: avoid_redundant_argument_values
|
// ignore_for_file: avoid_redundant_argument_values
|
||||||
@@ -29,8 +29,8 @@ import 'package:sharedinbox/core/repositories/mailbox_repository.dart' as _i7;
|
|||||||
// ignore_for_file: subtype_of_sealed_class
|
// ignore_for_file: subtype_of_sealed_class
|
||||||
// ignore_for_file: invalid_use_of_internal_member
|
// ignore_for_file: invalid_use_of_internal_member
|
||||||
|
|
||||||
class _FakeEmailBody_0 extends _i1.SmartFake implements _i2.EmailBody {
|
class _FakeMailbox_0 extends _i1.SmartFake implements _i2.Mailbox {
|
||||||
_FakeEmailBody_0(
|
_FakeMailbox_0(
|
||||||
Object parent,
|
Object parent,
|
||||||
Invocation parentInvocation,
|
Invocation parentInvocation,
|
||||||
) : super(
|
) : super(
|
||||||
@@ -39,9 +39,8 @@ class _FakeEmailBody_0 extends _i1.SmartFake implements _i2.EmailBody {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FakeSyncEmailsResult_1 extends _i1.SmartFake
|
class _FakeEmailBody_1 extends _i1.SmartFake implements _i3.EmailBody {
|
||||||
implements _i2.SyncEmailsResult {
|
_FakeEmailBody_1(
|
||||||
_FakeSyncEmailsResult_1(
|
|
||||||
Object parent,
|
Object parent,
|
||||||
Invocation parentInvocation,
|
Invocation parentInvocation,
|
||||||
) : super(
|
) : super(
|
||||||
@@ -50,9 +49,20 @@ class _FakeSyncEmailsResult_1 extends _i1.SmartFake
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FakeReliabilityResult_2 extends _i1.SmartFake
|
class _FakeSyncEmailsResult_2 extends _i1.SmartFake
|
||||||
implements _i2.ReliabilityResult {
|
implements _i3.SyncEmailsResult {
|
||||||
_FakeReliabilityResult_2(
|
_FakeSyncEmailsResult_2(
|
||||||
|
Object parent,
|
||||||
|
Invocation parentInvocation,
|
||||||
|
) : super(
|
||||||
|
parent,
|
||||||
|
parentInvocation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FakeReliabilityResult_3 extends _i1.SmartFake
|
||||||
|
implements _i3.ReliabilityResult {
|
||||||
|
_FakeReliabilityResult_3(
|
||||||
Object parent,
|
Object parent,
|
||||||
Invocation parentInvocation,
|
Invocation parentInvocation,
|
||||||
) : super(
|
) : super(
|
||||||
@@ -64,32 +74,32 @@ class _FakeReliabilityResult_2 extends _i1.SmartFake
|
|||||||
/// A class which mocks [AccountRepository].
|
/// A class which mocks [AccountRepository].
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockAccountRepository extends _i1.Mock implements _i3.AccountRepository {
|
class MockAccountRepository extends _i1.Mock implements _i4.AccountRepository {
|
||||||
MockAccountRepository() {
|
MockAccountRepository() {
|
||||||
_i1.throwOnMissingStub(this);
|
_i1.throwOnMissingStub(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Stream<List<_i5.Account>> observeAccounts() => (super.noSuchMethod(
|
_i5.Stream<List<_i6.Account>> observeAccounts() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#observeAccounts,
|
#observeAccounts,
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Stream<List<_i5.Account>>.empty(),
|
returnValue: _i5.Stream<List<_i6.Account>>.empty(),
|
||||||
) as _i4.Stream<List<_i5.Account>>);
|
) as _i5.Stream<List<_i6.Account>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<_i5.Account?> getAccount(String? id) => (super.noSuchMethod(
|
_i5.Future<_i6.Account?> getAccount(String? id) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getAccount,
|
#getAccount,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<_i5.Account?>.value(),
|
returnValue: _i5.Future<_i6.Account?>.value(),
|
||||||
) as _i4.Future<_i5.Account?>);
|
) as _i5.Future<_i6.Account?>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> addAccount(
|
_i5.Future<void> addAccount(
|
||||||
_i5.Account? account,
|
_i6.Account? account,
|
||||||
String? password,
|
String? password,
|
||||||
) =>
|
) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
@@ -100,13 +110,13 @@ class MockAccountRepository extends _i1.Mock implements _i3.AccountRepository {
|
|||||||
password,
|
password,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> updateAccount(
|
_i5.Future<void> updateAccount(
|
||||||
_i5.Account? account, {
|
_i6.Account? account, {
|
||||||
String? password,
|
String? password,
|
||||||
}) =>
|
}) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
@@ -115,65 +125,65 @@ class MockAccountRepository extends _i1.Mock implements _i3.AccountRepository {
|
|||||||
[account],
|
[account],
|
||||||
{#password: password},
|
{#password: password},
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> removeAccount(String? id) => (super.noSuchMethod(
|
_i5.Future<void> removeAccount(String? id) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#removeAccount,
|
#removeAccount,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<String> getPassword(String? accountId) => (super.noSuchMethod(
|
_i5.Future<String> getPassword(String? accountId) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getPassword,
|
#getPassword,
|
||||||
[accountId],
|
[accountId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<String>.value(_i6.dummyValue<String>(
|
returnValue: _i5.Future<String>.value(_i7.dummyValue<String>(
|
||||||
this,
|
this,
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getPassword,
|
#getPassword,
|
||||||
[accountId],
|
[accountId],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
) as _i4.Future<String>);
|
) as _i5.Future<String>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A class which mocks [MailboxRepository].
|
/// A class which mocks [MailboxRepository].
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockMailboxRepository extends _i1.Mock implements _i7.MailboxRepository {
|
class MockMailboxRepository extends _i1.Mock implements _i8.MailboxRepository {
|
||||||
MockMailboxRepository() {
|
MockMailboxRepository() {
|
||||||
_i1.throwOnMissingStub(this);
|
_i1.throwOnMissingStub(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Stream<List<_i8.Mailbox>> observeMailboxes(String? accountId) =>
|
_i5.Stream<List<_i2.Mailbox>> observeMailboxes(String? accountId) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#observeMailboxes,
|
#observeMailboxes,
|
||||||
[accountId],
|
[accountId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Stream<List<_i8.Mailbox>>.empty(),
|
returnValue: _i5.Stream<List<_i2.Mailbox>>.empty(),
|
||||||
) as _i4.Stream<List<_i8.Mailbox>>);
|
) as _i5.Stream<List<_i2.Mailbox>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<int> syncMailboxes(String? accountId) => (super.noSuchMethod(
|
_i5.Future<int> syncMailboxes(String? accountId) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#syncMailboxes,
|
#syncMailboxes,
|
||||||
[accountId],
|
[accountId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<int>.value(0),
|
returnValue: _i5.Future<int>.value(0),
|
||||||
) as _i4.Future<int>);
|
) as _i5.Future<int>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<_i8.Mailbox?> findMailboxByRole(
|
_i5.Future<_i2.Mailbox?> findMailboxByRole(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? role,
|
String? role,
|
||||||
) =>
|
) =>
|
||||||
@@ -185,18 +195,46 @@ class MockMailboxRepository extends _i1.Mock implements _i7.MailboxRepository {
|
|||||||
role,
|
role,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<_i8.Mailbox?>.value(),
|
returnValue: _i5.Future<_i2.Mailbox?>.value(),
|
||||||
) as _i4.Future<_i8.Mailbox?>);
|
) as _i5.Future<_i2.Mailbox?>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> clearForResync(String? accountId) => (super.noSuchMethod(
|
_i5.Future<void> clearForResync(String? accountId) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#clearForResync,
|
#clearForResync,
|
||||||
[accountId],
|
[accountId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Mailbox> createMailboxWithRole(
|
||||||
|
String? accountId,
|
||||||
|
String? name,
|
||||||
|
String? role,
|
||||||
|
) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#createMailboxWithRole,
|
||||||
|
[
|
||||||
|
accountId,
|
||||||
|
name,
|
||||||
|
role,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
returnValue: _i5.Future<_i2.Mailbox>.value(_FakeMailbox_0(
|
||||||
|
this,
|
||||||
|
Invocation.method(
|
||||||
|
#createMailboxWithRole,
|
||||||
|
[
|
||||||
|
accountId,
|
||||||
|
name,
|
||||||
|
role,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
) as _i5.Future<_i2.Mailbox>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A class which mocks [EmailRepository].
|
/// A class which mocks [EmailRepository].
|
||||||
@@ -208,13 +246,13 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Stream<String> get onChangesQueued => (super.noSuchMethod(
|
_i5.Stream<String> get onChangesQueued => (super.noSuchMethod(
|
||||||
Invocation.getter(#onChangesQueued),
|
Invocation.getter(#onChangesQueued),
|
||||||
returnValue: _i4.Stream<String>.empty(),
|
returnValue: _i5.Stream<String>.empty(),
|
||||||
) as _i4.Stream<String>);
|
) as _i5.Stream<String>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Stream<List<_i2.Email>> observeEmails(
|
_i5.Stream<List<_i3.Email>> observeEmails(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? mailboxPath, {
|
String? mailboxPath, {
|
||||||
int? limit = 50,
|
int? limit = 50,
|
||||||
@@ -228,11 +266,11 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
],
|
],
|
||||||
{#limit: limit},
|
{#limit: limit},
|
||||||
),
|
),
|
||||||
returnValue: _i4.Stream<List<_i2.Email>>.empty(),
|
returnValue: _i5.Stream<List<_i3.Email>>.empty(),
|
||||||
) as _i4.Stream<List<_i2.Email>>);
|
) as _i5.Stream<List<_i3.Email>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Stream<List<_i2.EmailThread>> observeThreads(
|
_i5.Stream<List<_i3.EmailThread>> observeThreads(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? mailboxPath, {
|
String? mailboxPath, {
|
||||||
int? limit = 50,
|
int? limit = 50,
|
||||||
@@ -246,11 +284,11 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
],
|
],
|
||||||
{#limit: limit},
|
{#limit: limit},
|
||||||
),
|
),
|
||||||
returnValue: _i4.Stream<List<_i2.EmailThread>>.empty(),
|
returnValue: _i5.Stream<List<_i3.EmailThread>>.empty(),
|
||||||
) as _i4.Stream<List<_i2.EmailThread>>);
|
) as _i5.Stream<List<_i3.EmailThread>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Stream<List<_i2.Email>> observeEmailsInThread(
|
_i5.Stream<List<_i3.Email>> observeEmailsInThread(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? mailboxPath,
|
String? mailboxPath,
|
||||||
String? threadId,
|
String? threadId,
|
||||||
@@ -264,36 +302,36 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
threadId,
|
threadId,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Stream<List<_i2.Email>>.empty(),
|
returnValue: _i5.Stream<List<_i3.Email>>.empty(),
|
||||||
) as _i4.Stream<List<_i2.Email>>);
|
) as _i5.Stream<List<_i3.Email>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<_i2.Email?> getEmail(String? emailId) => (super.noSuchMethod(
|
_i5.Future<_i3.Email?> getEmail(String? emailId) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getEmail,
|
#getEmail,
|
||||||
[emailId],
|
[emailId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<_i2.Email?>.value(),
|
returnValue: _i5.Future<_i3.Email?>.value(),
|
||||||
) as _i4.Future<_i2.Email?>);
|
) as _i5.Future<_i3.Email?>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<_i2.EmailBody> getEmailBody(String? emailId) =>
|
_i5.Future<_i3.EmailBody> getEmailBody(String? emailId) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getEmailBody,
|
#getEmailBody,
|
||||||
[emailId],
|
[emailId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<_i2.EmailBody>.value(_FakeEmailBody_0(
|
returnValue: _i5.Future<_i3.EmailBody>.value(_FakeEmailBody_1(
|
||||||
this,
|
this,
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getEmailBody,
|
#getEmailBody,
|
||||||
[emailId],
|
[emailId],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
) as _i4.Future<_i2.EmailBody>);
|
) as _i5.Future<_i3.EmailBody>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<_i2.SyncEmailsResult> syncEmails(
|
_i5.Future<_i3.SyncEmailsResult> syncEmails(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? mailboxPath,
|
String? mailboxPath,
|
||||||
) =>
|
) =>
|
||||||
@@ -306,7 +344,7 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue:
|
returnValue:
|
||||||
_i4.Future<_i2.SyncEmailsResult>.value(_FakeSyncEmailsResult_1(
|
_i5.Future<_i3.SyncEmailsResult>.value(_FakeSyncEmailsResult_2(
|
||||||
this,
|
this,
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#syncEmails,
|
#syncEmails,
|
||||||
@@ -316,10 +354,10 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
) as _i4.Future<_i2.SyncEmailsResult>);
|
) as _i5.Future<_i3.SyncEmailsResult>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> setFlag(
|
_i5.Future<void> setFlag(
|
||||||
String? emailId, {
|
String? emailId, {
|
||||||
bool? seen,
|
bool? seen,
|
||||||
bool? flagged,
|
bool? flagged,
|
||||||
@@ -333,12 +371,12 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
#flagged: flagged,
|
#flagged: flagged,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> markAllAsRead(
|
_i5.Future<void> markAllAsRead(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? mailboxPath,
|
String? mailboxPath,
|
||||||
) =>
|
) =>
|
||||||
@@ -350,12 +388,12 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
mailboxPath,
|
mailboxPath,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> moveEmail(
|
_i5.Future<void> moveEmail(
|
||||||
String? emailId,
|
String? emailId,
|
||||||
String? destMailboxPath,
|
String? destMailboxPath,
|
||||||
) =>
|
) =>
|
||||||
@@ -367,23 +405,23 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
destMailboxPath,
|
destMailboxPath,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<String?> deleteEmail(String? emailId) => (super.noSuchMethod(
|
_i5.Future<String?> deleteEmail(String? emailId) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#deleteEmail,
|
#deleteEmail,
|
||||||
[emailId],
|
[emailId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<String?>.value(),
|
returnValue: _i5.Future<String?>.value(),
|
||||||
) as _i4.Future<String?>);
|
) as _i5.Future<String?>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> sendEmail(
|
_i5.Future<void> sendEmail(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
_i2.EmailDraft? draft,
|
_i3.EmailDraft? draft,
|
||||||
) =>
|
) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
@@ -393,14 +431,14 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
draft,
|
draft,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<String> downloadAttachment(
|
_i5.Future<String> downloadAttachment(
|
||||||
String? emailId,
|
String? emailId,
|
||||||
_i2.EmailAttachment? attachment,
|
_i3.EmailAttachment? attachment,
|
||||||
) =>
|
) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
@@ -410,7 +448,7 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
attachment,
|
attachment,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<String>.value(_i6.dummyValue<String>(
|
returnValue: _i5.Future<String>.value(_i7.dummyValue<String>(
|
||||||
this,
|
this,
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#downloadAttachment,
|
#downloadAttachment,
|
||||||
@@ -420,25 +458,25 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
) as _i4.Future<String>);
|
) as _i5.Future<String>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<String> fetchRawRfc822(String? emailId) => (super.noSuchMethod(
|
_i5.Future<String> fetchRawRfc822(String? emailId) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#fetchRawRfc822,
|
#fetchRawRfc822,
|
||||||
[emailId],
|
[emailId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<String>.value(_i6.dummyValue<String>(
|
returnValue: _i5.Future<String>.value(_i7.dummyValue<String>(
|
||||||
this,
|
this,
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#fetchRawRfc822,
|
#fetchRawRfc822,
|
||||||
[emailId],
|
[emailId],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
) as _i4.Future<String>);
|
) as _i5.Future<String>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<List<_i2.Email>> searchEmails(
|
_i5.Future<List<_i3.Email>> searchEmails(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? mailboxPath,
|
String? mailboxPath,
|
||||||
String? query,
|
String? query,
|
||||||
@@ -452,11 +490,11 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
query,
|
query,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<List<_i2.Email>>.value(<_i2.Email>[]),
|
returnValue: _i5.Future<List<_i3.Email>>.value(<_i3.Email>[]),
|
||||||
) as _i4.Future<List<_i2.Email>>);
|
) as _i5.Future<List<_i3.Email>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<List<_i2.Email>> searchEmailsGlobal(
|
_i5.Future<List<_i3.Email>> searchEmailsGlobal(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? query,
|
String? query,
|
||||||
) =>
|
) =>
|
||||||
@@ -468,11 +506,11 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
query,
|
query,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<List<_i2.Email>>.value(<_i2.Email>[]),
|
returnValue: _i5.Future<List<_i3.Email>>.value(<_i3.Email>[]),
|
||||||
) as _i4.Future<List<_i2.Email>>);
|
) as _i5.Future<List<_i3.Email>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<List<_i2.Email>> getEmailsByAddress(
|
_i5.Future<List<_i3.Email>> getEmailsByAddress(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? address,
|
String? address,
|
||||||
) =>
|
) =>
|
||||||
@@ -484,11 +522,11 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
address,
|
address,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<List<_i2.Email>>.value(<_i2.Email>[]),
|
returnValue: _i5.Future<List<_i3.Email>>.value(<_i3.Email>[]),
|
||||||
) as _i4.Future<List<_i2.Email>>);
|
) as _i5.Future<List<_i3.Email>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<List<_i2.EmailAddress>> searchAddresses(
|
_i5.Future<List<_i3.EmailAddress>> searchAddresses(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? query, {
|
String? query, {
|
||||||
int? limit = 10,
|
int? limit = 10,
|
||||||
@@ -503,11 +541,11 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
{#limit: limit},
|
{#limit: limit},
|
||||||
),
|
),
|
||||||
returnValue:
|
returnValue:
|
||||||
_i4.Future<List<_i2.EmailAddress>>.value(<_i2.EmailAddress>[]),
|
_i5.Future<List<_i3.EmailAddress>>.value(<_i3.EmailAddress>[]),
|
||||||
) as _i4.Future<List<_i2.EmailAddress>>);
|
) as _i5.Future<List<_i3.EmailAddress>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<int> flushPendingChanges(
|
_i5.Future<int> flushPendingChanges(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? password,
|
String? password,
|
||||||
) =>
|
) =>
|
||||||
@@ -519,42 +557,42 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
password,
|
password,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<int>.value(0),
|
returnValue: _i5.Future<int>.value(0),
|
||||||
) as _i4.Future<int>);
|
) as _i5.Future<int>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Stream<List<_i2.FailedMutation>> observeFailedMutations(
|
_i5.Stream<List<_i3.FailedMutation>> observeFailedMutations(
|
||||||
String? accountId) =>
|
String? accountId) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#observeFailedMutations,
|
#observeFailedMutations,
|
||||||
[accountId],
|
[accountId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Stream<List<_i2.FailedMutation>>.empty(),
|
returnValue: _i5.Stream<List<_i3.FailedMutation>>.empty(),
|
||||||
) as _i4.Stream<List<_i2.FailedMutation>>);
|
) as _i5.Stream<List<_i3.FailedMutation>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> discardMutation(int? id) => (super.noSuchMethod(
|
_i5.Future<void> discardMutation(int? id) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#discardMutation,
|
#discardMutation,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> retryMutation(int? id) => (super.noSuchMethod(
|
_i5.Future<void> retryMutation(int? id) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#retryMutation,
|
#retryMutation,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<bool> cancelPendingChange(
|
_i5.Future<bool> cancelPendingChange(
|
||||||
String? emailId,
|
String? emailId,
|
||||||
String? changeType,
|
String? changeType,
|
||||||
) =>
|
) =>
|
||||||
@@ -566,11 +604,11 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
changeType,
|
changeType,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<bool>.value(false),
|
returnValue: _i5.Future<bool>.value(false),
|
||||||
) as _i4.Future<bool>);
|
) as _i5.Future<bool>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> snoozeEmail(
|
_i5.Future<void> snoozeEmail(
|
||||||
String? emailId,
|
String? emailId,
|
||||||
DateTime? until,
|
DateTime? until,
|
||||||
) =>
|
) =>
|
||||||
@@ -582,32 +620,32 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
until,
|
until,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<int> wakeUpEmails(String? accountId) => (super.noSuchMethod(
|
_i5.Future<int> wakeUpEmails(String? accountId) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#wakeUpEmails,
|
#wakeUpEmails,
|
||||||
[accountId],
|
[accountId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<int>.value(0),
|
returnValue: _i5.Future<int>.value(0),
|
||||||
) as _i4.Future<int>);
|
) as _i5.Future<int>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> restoreEmails(List<_i2.Email>? emails) =>
|
_i5.Future<void> restoreEmails(List<_i3.Email>? emails) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#restoreEmails,
|
#restoreEmails,
|
||||||
[emails],
|
[emails],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<_i2.Email?> findEmailByMessageId(
|
_i5.Future<_i3.Email?> findEmailByMessageId(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? messageId,
|
String? messageId,
|
||||||
) =>
|
) =>
|
||||||
@@ -619,20 +657,20 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
messageId,
|
messageId,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<_i2.Email?>.value(),
|
returnValue: _i5.Future<_i3.Email?>.value(),
|
||||||
) as _i4.Future<_i2.Email?>);
|
) as _i5.Future<_i3.Email?>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<int> applySieveRules(String? accountId) => (super.noSuchMethod(
|
_i5.Future<int> applySieveRules(String? accountId) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#applySieveRules,
|
#applySieveRules,
|
||||||
[accountId],
|
[accountId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<int>.value(0),
|
returnValue: _i5.Future<int>.value(0),
|
||||||
) as _i4.Future<int>);
|
) as _i5.Future<int>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Stream<void> watchJmapPush(
|
_i5.Stream<void> watchJmapPush(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? password,
|
String? password,
|
||||||
) =>
|
) =>
|
||||||
@@ -644,11 +682,11 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
password,
|
password,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Stream<void>.empty(),
|
returnValue: _i5.Stream<void>.empty(),
|
||||||
) as _i4.Stream<void>);
|
) as _i5.Stream<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<_i2.ReliabilityResult> verifySyncReliability(
|
_i5.Future<_i3.ReliabilityResult> verifySyncReliability(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
String? mailboxPath,
|
String? mailboxPath,
|
||||||
) =>
|
) =>
|
||||||
@@ -661,7 +699,7 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
returnValue:
|
returnValue:
|
||||||
_i4.Future<_i2.ReliabilityResult>.value(_FakeReliabilityResult_2(
|
_i5.Future<_i3.ReliabilityResult>.value(_FakeReliabilityResult_3(
|
||||||
this,
|
this,
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#verifySyncReliability,
|
#verifySyncReliability,
|
||||||
@@ -671,15 +709,15 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
) as _i4.Future<_i2.ReliabilityResult>);
|
) as _i5.Future<_i3.ReliabilityResult>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Future<void> clearForResync(String? accountId) => (super.noSuchMethod(
|
_i5.Future<void> clearForResync(String? accountId) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#clearForResync,
|
#clearForResync,
|
||||||
[accountId],
|
[accountId],
|
||||||
),
|
),
|
||||||
returnValue: _i4.Future<void>.value(),
|
returnValue: _i5.Future<void>.value(),
|
||||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||||
) as _i4.Future<void>);
|
) as _i5.Future<void>);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:sharedinbox/data/repositories/mailbox_repository_impl.dart';
|
|||||||
|
|
||||||
import 'account_repository_impl_test.dart' show MapSecureStorage;
|
import 'account_repository_impl_test.dart' show MapSecureStorage;
|
||||||
import 'db_test_helper.dart';
|
import 'db_test_helper.dart';
|
||||||
|
import 'fake_imap.dart' show SnoozeSpyImapClient;
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const _account = Account(
|
const _account = Account(
|
||||||
@@ -432,5 +433,177 @@ void main() {
|
|||||||
expect(result, isNotNull);
|
expect(result, isNotNull);
|
||||||
expect(result!.role, 'inbox');
|
expect(result!.role, 'inbox');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('createMailboxWithRole', () {
|
||||||
|
test('IMAP: creates mailbox on server and persists with role', () async {
|
||||||
|
final spy = SnoozeSpyImapClient();
|
||||||
|
final db = openTestDatabase();
|
||||||
|
final accounts = AccountRepositoryImpl(db, MapSecureStorage());
|
||||||
|
final mailboxes = MailboxRepositoryImpl(
|
||||||
|
db,
|
||||||
|
accounts,
|
||||||
|
imapConnect: (_, __, ___) async => spy,
|
||||||
|
);
|
||||||
|
await accounts.addAccount(_account, 'pw');
|
||||||
|
|
||||||
|
final result = await mailboxes.createMailboxWithRole(
|
||||||
|
'acc-1',
|
||||||
|
'Archive',
|
||||||
|
'archive',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(spy.createdMailbox, 'Archive');
|
||||||
|
expect(result.name, 'Archive');
|
||||||
|
expect(result.role, 'archive');
|
||||||
|
expect(result.path, 'Archive');
|
||||||
|
|
||||||
|
final found = await mailboxes.findMailboxByRole('acc-1', 'archive');
|
||||||
|
expect(found, isNotNull);
|
||||||
|
expect(found!.name, 'Archive');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('JMAP: creates mailbox on server and persists with role', () async {
|
||||||
|
final r = _makeRepos(
|
||||||
|
httpClient: _mockJmap(
|
||||||
|
apiResponses: [
|
||||||
|
{
|
||||||
|
'sessionState': 'sess1',
|
||||||
|
'methodResponses': [
|
||||||
|
[
|
||||||
|
'Mailbox/set',
|
||||||
|
{
|
||||||
|
'accountId': 'acct1',
|
||||||
|
'created': {
|
||||||
|
'new-mailbox': {'id': 'mbx-archive'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'0',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await r.accounts.addAccount(_jmapAccount, 'pw');
|
||||||
|
|
||||||
|
final result = await r.mailboxes
|
||||||
|
.createMailboxWithRole('jmap-1', 'Archive', 'archive');
|
||||||
|
|
||||||
|
expect(result.name, 'Archive');
|
||||||
|
expect(result.role, 'archive');
|
||||||
|
expect(result.path, 'mbx-archive');
|
||||||
|
|
||||||
|
final found = await r.mailboxes.findMailboxByRole('jmap-1', 'archive');
|
||||||
|
expect(found, isNotNull);
|
||||||
|
expect(found!.name, 'Archive');
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'JMAP: throws when server returns no created ID',
|
||||||
|
() async {
|
||||||
|
final r = _makeRepos(
|
||||||
|
httpClient: _mockJmap(
|
||||||
|
apiResponses: [
|
||||||
|
{
|
||||||
|
'sessionState': 'sess1',
|
||||||
|
'methodResponses': [
|
||||||
|
[
|
||||||
|
'Mailbox/set',
|
||||||
|
{
|
||||||
|
'accountId': 'acct1',
|
||||||
|
'created': null,
|
||||||
|
'notCreated': {
|
||||||
|
'new-mailbox': {'type': 'serverFail'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'0',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await r.accounts.addAccount(_jmapAccount, 'pw');
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
r.mailboxes.createMailboxWithRole('jmap-1', 'Archive', 'archive'),
|
||||||
|
throwsA(isA<Exception>()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('syncMailboxes IMAP preserves manually-set role', () {
|
||||||
|
test('existing role is kept when server returns no special-use flag',
|
||||||
|
() async {
|
||||||
|
final spy = SnoozeSpyImapClient();
|
||||||
|
// Make listMailboxes return a plain folder without \Archive.
|
||||||
|
final db = openTestDatabase();
|
||||||
|
final accounts = AccountRepositoryImpl(db, MapSecureStorage());
|
||||||
|
|
||||||
|
// Override listMailboxes to return one plain folder.
|
||||||
|
final fakeClient = _PlainArchiveImapClient();
|
||||||
|
final mailboxes = MailboxRepositoryImpl(
|
||||||
|
db,
|
||||||
|
accounts,
|
||||||
|
imapConnect: (_, __, ___) async => fakeClient,
|
||||||
|
);
|
||||||
|
await accounts.addAccount(_account, 'pw');
|
||||||
|
|
||||||
|
// Pre-seed the DB with role='archive' (as if user created the folder).
|
||||||
|
await db.into(db.mailboxes).insert(
|
||||||
|
MailboxesCompanion.insert(
|
||||||
|
id: 'acc-1:Archive',
|
||||||
|
accountId: 'acc-1',
|
||||||
|
path: 'Archive',
|
||||||
|
name: 'Archive',
|
||||||
|
role: const Value('archive'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await mailboxes.syncMailboxes('acc-1');
|
||||||
|
|
||||||
|
final found = await mailboxes.findMailboxByRole('acc-1', 'archive');
|
||||||
|
expect(
|
||||||
|
found,
|
||||||
|
isNotNull,
|
||||||
|
reason: 'Manually-set role should be preserved after sync',
|
||||||
|
);
|
||||||
|
expect(found!.path, 'Archive');
|
||||||
|
// Suppress unused warning on spy.
|
||||||
|
expect(spy, isNotNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fake IMAP client that lists one mailbox named 'Archive' without any
|
||||||
|
/// special-use flags, and logs out cleanly.
|
||||||
|
class _PlainArchiveImapClient extends SnoozeSpyImapClient {
|
||||||
|
@override
|
||||||
|
Future<List<imap.Mailbox>> listMailboxes({
|
||||||
|
String path = '""',
|
||||||
|
bool recursive = false,
|
||||||
|
List<String>? mailboxPatterns,
|
||||||
|
List<String>? selectionOptions,
|
||||||
|
List<imap.ReturnOption>? returnOptions,
|
||||||
|
}) async =>
|
||||||
|
[
|
||||||
|
imap.Mailbox(
|
||||||
|
encodedName: 'Archive',
|
||||||
|
encodedPath: 'Archive',
|
||||||
|
pathSeparator: '/',
|
||||||
|
flags: [], // No \Archive special-use flag
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<imap.Mailbox> statusMailbox(
|
||||||
|
imap.Mailbox mailbox,
|
||||||
|
List<imap.StatusFlags> flags,
|
||||||
|
) async =>
|
||||||
|
mailbox;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<dynamic> logout() async {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,6 +62,21 @@ class _FakeMailboxes implements MailboxRepository {
|
|||||||
null;
|
null;
|
||||||
@override
|
@override
|
||||||
Future<void> clearForResync(String accountId) async {}
|
Future<void> clearForResync(String accountId) async {}
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailboxWithRole(
|
||||||
|
String accountId,
|
||||||
|
String name,
|
||||||
|
String role,
|
||||||
|
) async =>
|
||||||
|
Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
role: role,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FakeEmails implements EmailRepository {
|
class _FakeEmails implements EmailRepository {
|
||||||
|
|||||||
@@ -54,6 +54,21 @@ class _FakeMailboxes implements MailboxRepository {
|
|||||||
null;
|
null;
|
||||||
@override
|
@override
|
||||||
Future<void> clearForResync(String accountId) async {}
|
Future<void> clearForResync(String accountId) async {}
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailboxWithRole(
|
||||||
|
String accountId,
|
||||||
|
String name,
|
||||||
|
String role,
|
||||||
|
) async =>
|
||||||
|
Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
role: role,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CountingEmails implements EmailRepository {
|
class _CountingEmails implements EmailRepository {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:sharedinbox/core/models/email.dart';
|
import 'package:sharedinbox/core/models/email.dart';
|
||||||
|
import 'package:sharedinbox/core/models/mailbox.dart';
|
||||||
import 'package:sharedinbox/di.dart';
|
import 'package:sharedinbox/di.dart';
|
||||||
import 'package:sharedinbox/ui/screens/email_detail_screen.dart';
|
import 'package:sharedinbox/ui/screens/email_detail_screen.dart';
|
||||||
import 'package:sharedinbox/ui/screens/email_list_screen.dart';
|
import 'package:sharedinbox/ui/screens/email_list_screen.dart';
|
||||||
@@ -631,5 +632,150 @@ void main() {
|
|||||||
|
|
||||||
expect(find.text('This is the preview text'), findsOneWidget);
|
expect(find.text('This is the preview text'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('archive with missing folder', () {
|
||||||
|
testWidgets('shows dialog when archive folder is not found', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final email = testEmail(subject: 'To archive');
|
||||||
|
await tester.pumpWidget(
|
||||||
|
buildApp(
|
||||||
|
initialLocation: '/accounts/acc-1/mailboxes/INBOX/emails',
|
||||||
|
overrides: [
|
||||||
|
accountRepositoryProvider.overrideWithValue(
|
||||||
|
FakeAccountRepository([kTestAccount]),
|
||||||
|
),
|
||||||
|
// No archive folder in the repo.
|
||||||
|
mailboxRepositoryProvider.overrideWithValue(
|
||||||
|
FakeMailboxRepository(),
|
||||||
|
),
|
||||||
|
emailRepositoryProvider.overrideWithValue(
|
||||||
|
FakeEmailRepository(emails: [email]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Enter selection mode and tap archive.
|
||||||
|
await tester.longPress(find.text('To archive'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.byIcon(Icons.archive));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('No archive folder found'), findsOneWidget);
|
||||||
|
expect(find.text('Choose existing folder'), findsOneWidget);
|
||||||
|
expect(find.text('Create "Archive"'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('tapping Create creates the folder and moves emails', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final email = testEmail(subject: 'To archive');
|
||||||
|
final movedTo = <String>[];
|
||||||
|
|
||||||
|
final fakeEmailRepo = _SpyEmailRepository(
|
||||||
|
emails: [email],
|
||||||
|
onMove: (id, path) => movedTo.add(path),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
buildApp(
|
||||||
|
initialLocation: '/accounts/acc-1/mailboxes/INBOX/emails',
|
||||||
|
overrides: [
|
||||||
|
accountRepositoryProvider.overrideWithValue(
|
||||||
|
FakeAccountRepository([kTestAccount]),
|
||||||
|
),
|
||||||
|
mailboxRepositoryProvider.overrideWithValue(
|
||||||
|
FakeMailboxRepository(),
|
||||||
|
),
|
||||||
|
emailRepositoryProvider.overrideWithValue(fakeEmailRepo),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.longPress(find.text('To archive'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.byIcon(Icons.archive));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Tap "Create Archive".
|
||||||
|
await tester.tap(find.text('Create "Archive"'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(movedTo, contains('Archive'));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'tapping Choose existing opens folder picker and moves emails',
|
||||||
|
(tester) async {
|
||||||
|
final email = testEmail(subject: 'To archive');
|
||||||
|
final movedTo = <String>[];
|
||||||
|
|
||||||
|
final fakeEmailRepo = _SpyEmailRepository(
|
||||||
|
emails: [email],
|
||||||
|
onMove: (id, path) => movedTo.add(path),
|
||||||
|
);
|
||||||
|
const archiveFolder = Mailbox(
|
||||||
|
id: 'acc-1:OldArchive',
|
||||||
|
accountId: 'acc-1',
|
||||||
|
path: 'OldArchive',
|
||||||
|
name: 'OldArchive',
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
buildApp(
|
||||||
|
initialLocation: '/accounts/acc-1/mailboxes/INBOX/emails',
|
||||||
|
overrides: [
|
||||||
|
accountRepositoryProvider.overrideWithValue(
|
||||||
|
FakeAccountRepository([kTestAccount]),
|
||||||
|
),
|
||||||
|
// Repo has a folder but it has no 'archive' role.
|
||||||
|
mailboxRepositoryProvider.overrideWithValue(
|
||||||
|
FakeMailboxRepository([archiveFolder]),
|
||||||
|
),
|
||||||
|
emailRepositoryProvider.overrideWithValue(fakeEmailRepo),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.longPress(find.text('To archive'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.byIcon(Icons.archive));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Tap "Choose existing folder".
|
||||||
|
await tester.tap(find.text('Choose existing folder'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Bottom sheet with folder list appears.
|
||||||
|
expect(find.text('OldArchive'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text('OldArchive'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(movedTo, contains('OldArchive'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Email repository spy that records [moveEmail] calls.
|
||||||
|
class _SpyEmailRepository extends FakeEmailRepository {
|
||||||
|
_SpyEmailRepository({
|
||||||
|
super.emails,
|
||||||
|
required void Function(String emailId, String path) onMove,
|
||||||
|
}) : _onMove = onMove;
|
||||||
|
|
||||||
|
final void Function(String emailId, String path) _onMove;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> moveEmail(String emailId, String destMailboxPath) async {
|
||||||
|
_onMove(emailId, destMailboxPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -164,8 +164,28 @@ class FakeMailboxRepository implements MailboxRepository {
|
|||||||
@override
|
@override
|
||||||
Future<Mailbox?> findMailboxByRole(String accountId, String role) async =>
|
Future<Mailbox?> findMailboxByRole(String accountId, String role) async =>
|
||||||
_mailboxes.where((m) => m.role == role).firstOrNull;
|
_mailboxes.where((m) => m.role == role).firstOrNull;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> clearForResync(String accountId) async {}
|
Future<void> clearForResync(String accountId) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailboxWithRole(
|
||||||
|
String accountId,
|
||||||
|
String name,
|
||||||
|
String role,
|
||||||
|
) async {
|
||||||
|
final mailbox = Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
role: role,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
|
_mailboxes.add(mailbox);
|
||||||
|
return mailbox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeEmailRepository implements EmailRepository {
|
class FakeEmailRepository implements EmailRepository {
|
||||||
|
|||||||
Reference in New Issue
Block a user