231 lines
7.6 KiB
Dart
231 lines
7.6 KiB
Dart
import 'dart:io';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:ikar/backend/modules/chats.dart';
|
||
import 'package:ikar/backend/modules/messages.dart';
|
||
import 'package:ikar/core/config/qlyra_settings.dart';
|
||
import 'package:ikar/core/storage/app_database.dart';
|
||
import 'package:ikar/core/storage/chat_activity_store.dart';
|
||
import 'package:ikar/core/utils/format.dart';
|
||
import 'package:ikar/frontend/widgets/attachment/bubbles/meta_marks.dart';
|
||
import 'package:ikar/frontend/widgets/message_bubble.dart';
|
||
import 'package:ikar/l10n/app_localizations.dart';
|
||
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
|
||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||
|
||
class _SyntheticPathProvider extends PathProviderPlatform
|
||
with MockPlatformInterfaceMixin {
|
||
final String directory;
|
||
|
||
_SyntheticPathProvider(this.directory);
|
||
|
||
@override
|
||
Future<String?> getApplicationSupportPath() async => directory;
|
||
}
|
||
|
||
const int _me = 1;
|
||
const int _chatId = 900201;
|
||
const int _alice = 900301;
|
||
const int _bob = 900302;
|
||
|
||
CachedMessage _message({int? typingMs}) =>
|
||
CachedMessage.fromPushPayload(_me, _chatId, {
|
||
'id': '7101',
|
||
'time': DateTime(2026, 1, 1, 12, 30).millisecondsSinceEpoch,
|
||
'type': 'USER',
|
||
'sender': _alice,
|
||
'text': 'Синтетическое сообщение',
|
||
}).copyWith(typingMs: typingMs);
|
||
|
||
Future<void> _pumpBubble(WidgetTester tester, CachedMessage message) async {
|
||
await tester.pumpWidget(
|
||
MaterialApp(
|
||
locale: const Locale('ru'),
|
||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||
supportedLocales: AppLocalizations.supportedLocales,
|
||
home: Scaffold(
|
||
body: Align(
|
||
alignment: Alignment.topLeft,
|
||
child: MessageBubble(
|
||
message: message,
|
||
isMe: false,
|
||
myId: _me,
|
||
chatType: 'DIALOG',
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
await tester.pump();
|
||
}
|
||
|
||
Finder get _marks => find.descendant(
|
||
of: find.byType(TypingTimeMark),
|
||
matching: find.byType(Icon),
|
||
);
|
||
|
||
void main() {
|
||
TestWidgetsFlutterBinding.ensureInitialized();
|
||
|
||
group('Отсчёт времени набора', () {
|
||
late int now;
|
||
late ChatActivityStore store;
|
||
|
||
setUp(() {
|
||
now = 1000000;
|
||
store = ChatActivityStore.withClock(() => now);
|
||
});
|
||
|
||
void typing(int userId) => store.mark(_chatId, userId, ChatActivity.typing);
|
||
|
||
testWidgets('считает от первого «печатает» до сообщения', (tester) async {
|
||
typing(_alice);
|
||
now += 4000;
|
||
await tester.pump(const Duration(seconds: 4));
|
||
typing(_alice);
|
||
now += 3500;
|
||
await tester.pump(const Duration(milliseconds: 3500));
|
||
|
||
expect(store.takeTypingTime(_chatId, _alice), 7500);
|
||
expect(store.takeTypingTime(_chatId, _alice), isNull);
|
||
store.clearChat(_chatId);
|
||
});
|
||
|
||
testWidgets('истёкший статус сбрасывает отсчёт', (tester) async {
|
||
typing(_alice);
|
||
now += 7000;
|
||
await tester.pump(const Duration(seconds: 7));
|
||
expect(store.takeTypingTime(_chatId, _alice), isNull);
|
||
|
||
typing(_alice);
|
||
now += 2000;
|
||
await tester.pump(const Duration(seconds: 2));
|
||
expect(store.takeTypingTime(_chatId, _alice), 2000);
|
||
store.clearChat(_chatId);
|
||
});
|
||
|
||
testWidgets('в группе у каждого участника свой отсчёт', (tester) async {
|
||
typing(_alice);
|
||
now += 3000;
|
||
await tester.pump(const Duration(seconds: 3));
|
||
typing(_bob);
|
||
typing(_alice);
|
||
now += 2000;
|
||
await tester.pump(const Duration(seconds: 2));
|
||
|
||
expect(store.takeTypingTime(_chatId, _bob), 2000);
|
||
expect(store.takeTypingTime(_chatId, _alice), 5000);
|
||
store.clearChat(_chatId);
|
||
});
|
||
|
||
testWidgets('выбор стикера и снятие статуса обнуляют отсчёт', (
|
||
tester,
|
||
) async {
|
||
typing(_alice);
|
||
now += 2000;
|
||
store.mark(_chatId, _alice, ChatActivity.sticker);
|
||
expect(store.takeTypingTime(_chatId, _alice), isNull);
|
||
|
||
typing(_bob);
|
||
now += 2000;
|
||
store.clearUser(_chatId, _bob);
|
||
expect(store.takeTypingTime(_chatId, _bob), isNull);
|
||
store.clearChat(_chatId);
|
||
});
|
||
|
||
testWidgets('набор дольше пятнадцати минут не засчитывается', (
|
||
tester,
|
||
) async {
|
||
typing(_alice);
|
||
now += ChatActivityStore.maxTypingTime.inMilliseconds + 1;
|
||
|
||
expect(store.takeTypingTime(_chatId, _alice), isNull);
|
||
store.clearChat(_chatId);
|
||
});
|
||
});
|
||
|
||
test('секунды набора округляются до десятых, минуты — до сотых', () {
|
||
final ru = lookupAppLocalizations(const Locale('ru'));
|
||
expect(formatApproxDuration(ru, 240), '0,2 с');
|
||
expect(formatApproxDuration(ru, 7000), '7,0 с');
|
||
expect(formatApproxDuration(ru, 7460), '7,5 с');
|
||
expect(formatApproxDuration(ru, 59940), '59,9 с');
|
||
expect(formatApproxDuration(ru, 59960), '1,00 мин');
|
||
expect(formatApproxDuration(ru, 150000), '2,50 мин');
|
||
expect(formatApproxDuration(ru, 208200), '3,47 мин');
|
||
expect(formatApproxDuration(ru, 843000), '14,05 мин');
|
||
});
|
||
|
||
test('время набора переживает сохранение сообщения в базу', () async {
|
||
final directory = Directory.systemTemp.createTempSync(
|
||
'synthetic_typing_time_test',
|
||
);
|
||
PathProviderPlatform.instance = _SyntheticPathProvider(directory.path);
|
||
addTearDown(() async {
|
||
await AppDatabase.close();
|
||
if (directory.existsSync()) directory.deleteSync(recursive: true);
|
||
});
|
||
await AppDatabase.init();
|
||
await AppDatabase.saveProfile(
|
||
ProfileData(
|
||
id: _me,
|
||
firstName: 'Synthetic owner',
|
||
phone: 100000,
|
||
country: 'ZZ',
|
||
accountStatus: 0,
|
||
updateTime: 1,
|
||
),
|
||
);
|
||
await chats.cacheServerChat({
|
||
'id': _chatId,
|
||
'type': 'DIALOG',
|
||
'status': 'ACTIVE',
|
||
'participants': {'$_me': 0, '$_alice': 0},
|
||
}, _me);
|
||
|
||
await AppDatabase.saveMessages([_message(typingMs: 7500).toDbRow()]);
|
||
|
||
final row = await AppDatabase.loadMessage(_me, _chatId, '7101');
|
||
expect(CachedMessage.fromDbRow(row!).typingMs, 7500);
|
||
});
|
||
|
||
group('Метка времени набора', () {
|
||
setUp(() => QlyraSettings.showTypingTime.value = true);
|
||
tearDown(() => QlyraSettings.showTypingTime.value = false);
|
||
|
||
testWidgets('стоит слева от времени и по тапу называет длительность', (
|
||
tester,
|
||
) async {
|
||
await _pumpBubble(tester, _message(typingMs: 7500));
|
||
|
||
final mark = tester.getRect(_marks);
|
||
final clock = tester.getRect(find.text('12:30'));
|
||
expect(mark.right, lessThanOrEqualTo(clock.left));
|
||
|
||
await tester.tap(_marks);
|
||
await tester.pump();
|
||
await tester.pump(const Duration(milliseconds: 200));
|
||
expect(find.text('Сообщение печаталось примерно ~7,5 с'), findsOneWidget);
|
||
|
||
await tester.pump(const Duration(seconds: 3));
|
||
await tester.pumpAndSettle();
|
||
});
|
||
|
||
testWidgets('сообщение без насчитанного времени остаётся без метки', (
|
||
tester,
|
||
) async {
|
||
await _pumpBubble(tester, _message());
|
||
|
||
expect(_marks, findsNothing);
|
||
});
|
||
|
||
testWidgets('выключенная настройка прячет метку', (tester) async {
|
||
QlyraSettings.showTypingTime.value = false;
|
||
await _pumpBubble(tester, _message(typingMs: 7500));
|
||
|
||
expect(_marks, findsNothing);
|
||
});
|
||
});
|
||
}
|