Tests / test (push) Canceled after 0s
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Native crypto / native-crypto (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
176 lines
6.3 KiB
Dart
176 lines
6.3 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:ikar/backend/modules/contacts.dart';
|
|
import 'package:ikar/backend/modules/messages.dart';
|
|
import 'package:ikar/core/contacts/startup_contact_names.dart';
|
|
import 'package:ikar/core/storage/app_database.dart';
|
|
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
|
|
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
class _Paths extends PathProviderPlatform with MockPlatformInterfaceMixin {
|
|
_Paths(this.directory);
|
|
final String directory;
|
|
@override
|
|
Future<String?> getApplicationSupportPath() async => directory;
|
|
}
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({});
|
|
ContactCache.resetMemoryForTesting();
|
|
});
|
|
tearDown(ContactCache.resetMemoryForTesting);
|
|
|
|
testWidgets('first frame waits for local contact and phonebook names', (
|
|
tester,
|
|
) async {
|
|
await tester.runAsync(() async {
|
|
final directory = Directory.systemTemp.createTempSync(
|
|
'ikar_startup_names',
|
|
);
|
|
final previousPaths = PathProviderPlatform.instance;
|
|
PathProviderPlatform.instance = _Paths(directory.path);
|
|
try {
|
|
await AppDatabase.init();
|
|
await AppDatabase.saveProfile(ProfileData.stub(1));
|
|
await ContactsModule.syncFromLoginPayload({
|
|
'contacts': [
|
|
{
|
|
'id': 7,
|
|
'phone': 79000000007,
|
|
'names': [
|
|
{'type': 'CUSTOM', 'firstName': 'Имя из контактов'},
|
|
],
|
|
},
|
|
],
|
|
}, 1);
|
|
ContactCache.put(7, 'Имя из профиля');
|
|
final phonebookRead = Completer<void>();
|
|
final startedPhonebook = Completer<void>();
|
|
final ready = prepareStartupContactNames(
|
|
accountReady: Future.value(1),
|
|
settingsReady: Future.value(),
|
|
cacheReady: ContactCache.load(),
|
|
primeContacts: ContactsModule.primeCacheFromDb,
|
|
loadPhonebook: () {
|
|
startedPhonebook.complete();
|
|
return phonebookRead.future;
|
|
},
|
|
onError: (_, error, _) => fail('$error'),
|
|
);
|
|
var firstFrameReady = false;
|
|
final startup = ready.then((_) => firstFrameReady = true);
|
|
await startedPhonebook.future;
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(firstFrameReady, isFalse);
|
|
phonebookRead.complete();
|
|
await startup;
|
|
} finally {
|
|
await AppDatabase.close();
|
|
PathProviderPlatform.instance = previousPaths;
|
|
directory.deleteSync(recursive: true);
|
|
}
|
|
});
|
|
await tester.pumpWidget(MaterialApp(home: Text(ContactCache.get(7)!)));
|
|
expect(find.text('Имя из контактов'), findsOneWidget);
|
|
expect(find.text('Имя из профиля'), findsNothing);
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('Имя из контактов'), findsOneWidget);
|
|
});
|
|
|
|
test(
|
|
'restored cache cannot overwrite database names during startup',
|
|
() async {
|
|
final cache = Completer<void>();
|
|
final settings = Completer<void>();
|
|
final calls = <String>[];
|
|
final ready = prepareStartupContactNames(
|
|
accountReady: Future.value(42),
|
|
settingsReady: settings.future,
|
|
cacheReady: cache.future,
|
|
primeContacts: (id) async => calls.add('contacts:$id'),
|
|
loadPhonebook: () async => calls.add('phonebook'),
|
|
onError: (_, error, _) => fail('$error'),
|
|
);
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(calls, isEmpty);
|
|
settings.complete();
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(calls, isEmpty);
|
|
cache.complete();
|
|
await ready;
|
|
expect(calls, ['contacts:42', 'phonebook']);
|
|
},
|
|
);
|
|
|
|
test('without an account there is no contact database read', () async {
|
|
final calls = <String>[];
|
|
await prepareStartupContactNames(
|
|
accountReady: Future.value(null),
|
|
settingsReady: Future.value(),
|
|
cacheReady: Future.value(),
|
|
primeContacts: (_) async => calls.add('contacts'),
|
|
loadPhonebook: () async => calls.add('phonebook'),
|
|
onError: (_, error, _) => fail('$error'),
|
|
);
|
|
expect(calls, ['phonebook']);
|
|
});
|
|
|
|
test(
|
|
'unavailable phonebook leaves cached names and does not block startup',
|
|
() async {
|
|
final errors = <String>[];
|
|
await prepareStartupContactNames(
|
|
accountReady: Future.value(1),
|
|
settingsReady: Future.value(),
|
|
cacheReady: Future.value(),
|
|
primeContacts: (_) async => ContactCache.put(7, 'Локальное имя'),
|
|
loadPhonebook: () async => throw StateError('Permission unavailable'),
|
|
onError: (source, _, _) => errors.add(source),
|
|
);
|
|
expect(ContactCache.get(7), 'Локальное имя');
|
|
expect(errors, ['phonebook']);
|
|
},
|
|
);
|
|
|
|
testWidgets('phone numbers survive a cache save after a cold load', (
|
|
tester,
|
|
) async {
|
|
SharedPreferences.setMockInitialValues({
|
|
'contact_cache_v1': jsonEncode({
|
|
'7': {'n': 'Серверное имя', 'p': 79000000007},
|
|
'8': {'p': 79000000008},
|
|
'9': {'n': 'Старый формат'},
|
|
}),
|
|
});
|
|
await ContactCache.load();
|
|
ContactCache.put(7, 'Новое серверное имя');
|
|
await tester.pump(const Duration(seconds: 3));
|
|
await tester.pump();
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final stored = jsonDecode(prefs.getString('contact_cache_v1')!) as Map;
|
|
expect(stored['7']['p'], 79000000007);
|
|
expect(stored['8']['p'], 79000000008);
|
|
expect(stored['9']['n'], 'Старый формат');
|
|
});
|
|
|
|
testWidgets('phone-only updates are saved and invalid numbers are ignored', (
|
|
tester,
|
|
) async {
|
|
ContactCache.putPhone(7, 79000000007);
|
|
ContactCache.putPhone(8, 0);
|
|
await tester.pump(const Duration(seconds: 3));
|
|
await tester.pump();
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final stored = jsonDecode(prefs.getString('contact_cache_v1')!) as Map;
|
|
expect(stored['7']['p'], 79000000007);
|
|
expect(stored.containsKey('8'), isFalse);
|
|
});
|
|
}
|