import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:ikar/backend/modules/messages.dart'; import 'package:ikar/core/config/app_message_status_size.dart'; import 'package:ikar/frontend/widgets/message_bubble.dart'; import 'package:ikar/frontend/widgets/message_status_icon.dart'; import 'package:ikar/frontend/widgets/text_with_meta.dart'; import 'package:ikar/l10n/app_localizations.dart'; void main() { setUp(() => AppMessageStatusSize.current.value = 1); tearDown(() => AppMessageStatusSize.current.value = 1); testWidgets('receipt icons retain their base size and resize live', ( tester, ) async { await tester.pumpWidget( const MaterialApp( home: Scaffold( body: Row( children: [ MessageStatusIcon(icon: Symbols.check, size: 12), MessageStatusIcon(icon: Symbols.done, size: 13), MessageStatusIcon(icon: Symbols.done_all, size: 14), ], ), ), ), ); void expectSizes(double scale) { for (final (icon, baseSize) in [ (Symbols.check, 12.0), (Symbols.done, 13.0), (Symbols.done_all, 14.0), ]) { final finder = find.byIcon(icon); expect(tester.widget(finder).size, baseSize * scale); expect(tester.getSize(finder), Size.square(baseSize * scale)); } } expectSizes(1); AppMessageStatusSize.current.value = 1.237; await tester.pump(); expectSizes(1.237); AppMessageStatusSize.current.value = 1.5; await tester.pump(); expectSizes(1.5); expect(tester.takeException(), isNull); }); testWidgets('error and pending icons keep their size', (tester) async { await tester.pumpWidget( const MaterialApp( home: Scaffold( body: Row( children: [ MessageStatusIcon(icon: Symbols.error, size: 14), MessageStatusIcon(icon: Symbols.schedule, size: 14), Icon(Symbols.check, size: 14), ], ), ), ), ); AppMessageStatusSize.current.value = 1.5; await tester.pump(); for (final icon in [Symbols.error, Symbols.schedule, Symbols.check]) { final finder = find.byIcon(icon); expect(tester.widget(finder).size, 14); expect(tester.getSize(finder), const Size.square(14)); } }); for (final text in ['Hi', 'A longer message that fills several lines']) { testWidgets('enlarged receipts remain inside text metadata: $text', ( tester, ) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Align( alignment: Alignment.topLeft, child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 140), child: TextWithMeta( key: const ValueKey('text-with-meta'), text: Text(text, style: const TextStyle(fontSize: 16)), meta: const Row( mainAxisSize: MainAxisSize.min, children: [ Text('12:34', style: TextStyle(fontSize: 10)), SizedBox(width: 4), MessageStatusIcon(icon: Symbols.done_all, size: 14), ], ), ), ), ), ), ), ); AppMessageStatusSize.current.value = 1.5; await tester.pump(); final parent = tester.getRect( find.byKey(const ValueKey('text-with-meta')), ); final icon = tester.getRect(find.byIcon(Symbols.done_all)); expect(icon.size, const Size.square(21)); expect(icon.left, greaterThanOrEqualTo(parent.left)); expect(icon.top, greaterThanOrEqualTo(parent.top)); expect(icon.right, lessThanOrEqualTo(parent.right)); expect(icon.bottom, lessThanOrEqualTo(parent.bottom)); expect(tester.takeException(), isNull); }); } testWidgets('padded receipts fit the final line of a multiline caption', ( tester, ) async { const caption = 'First line\nHi'; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Align( alignment: Alignment.topLeft, child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 200), child: TextWithMeta( key: const ValueKey('caption-with-meta'), fillWidth: true, text: const Text( caption, style: TextStyle(fontSize: 16, height: 1.3), ), meta: const Padding( key: ValueKey('caption-meta'), padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text('12:34', style: TextStyle(fontSize: 10)), SizedBox(width: 4), MessageStatusIcon(icon: Symbols.done_all, size: 14), ], ), ), ), ), ), ), ), ); AppMessageStatusSize.current.value = 1.5; await tester.pump(); final paragraph = tester.renderObject(find.text(caption)); final caret = paragraph.getOffsetForCaret( TextPosition(offset: caption.length), Rect.zero, ); final finalLineTop = paragraph.localToGlobal(caret).dy; final text = tester.getRect(find.text(caption)); final parent = tester.getRect( find.byKey(const ValueKey('caption-with-meta')), ); final meta = tester.getRect(find.byKey(const ValueKey('caption-meta'))); expect(caret.dy, greaterThan(0)); expect(caret.dx + 8 + meta.width, lessThanOrEqualTo(parent.width)); expect(meta.height, 25); expect(meta.top, greaterThanOrEqualTo(finalLineTop - 0.01)); expect(meta.top, lessThan(text.bottom)); expect(meta.bottom, lessThanOrEqualTo(parent.bottom)); expect( tester.getSize(find.byIcon(Symbols.done_all)), const Size.square(21), ); expect(tester.takeException(), isNull); }); testWidgets('an existing own message receives scale and read updates', ( tester, ) async { final readTime = ValueNotifier(0); addTearDown(readTime.dispose); final message = CachedMessage( id: 'status-scale-test', accountId: 1, chatId: 2, senderId: 1, text: 'Hi', time: 1000, status: 'sent', ); await tester.pumpWidget( MaterialApp( locale: const Locale('ru'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: Scaffold( body: Align( alignment: Alignment.topRight, child: MessageBubble( message: message, isMe: true, myId: 1, chatType: 'DIALOG', otherReadTime: readTime, ), ), ), ), ); expect(tester.widget(find.byIcon(Symbols.check)).size, 14); AppMessageStatusSize.current.value = 1.5; await tester.pump(); expect(tester.widget(find.byIcon(Symbols.check)).size, 21); readTime.value = message.time; await tester.pump(); expect(find.byIcon(Symbols.check), findsNothing); expect(tester.widget(find.byIcon(Symbols.done_all)).size, 21); expect(tester.takeException(), isNull); }); }