Files
IKAR/test/visual_regressions_test.dart

263 lines
7.4 KiB
Dart

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ikar/frontend/screens/chats/chat/view/chat_list_tile.dart';
import 'package:ikar/frontend/widgets/custom_notification.dart';
import 'package:ikar/l10n/app_localizations.dart';
import 'package:ikar/frontend/widgets/reply_preview.dart';
import 'package:ikar/frontend/widgets/toast_placement.dart';
import 'package:ikar/frontend/widgets/undo_notification.dart';
import 'package:ikar/models/attachment.dart';
int _mounts = 0;
class _MountCounter extends StatefulWidget {
final String label;
const _MountCounter(this.label);
@override
State<_MountCounter> createState() => _MountCounterState();
}
class _MountCounterState extends State<_MountCounter> {
@override
void initState() {
super.initState();
_mounts++;
}
@override
Widget build(BuildContext context) =>
SizedBox(height: 72, child: Text(widget.label));
}
Widget _chatList(List<String> order, int revision) => MaterialApp(
home: Scaffold(
body: ListView(
children: [
for (final id in order)
AnimatedChatTile(
key: ValueKey('chat_$id'),
id: id,
revision: revision,
isNew: false,
child: _MountCounter(id),
),
],
),
),
);
Widget _notificationHost() => MaterialApp(
home: Builder(
builder: (context) => Scaffold(
body: Center(
child: TextButton(
onPressed: () => showCustomNotification(context, 'Синтетика'),
child: const Text('show'),
),
),
),
),
);
void _useScreen(WidgetTester tester, {double keyboard = 0}) {
tester.view.devicePixelRatio = 1;
tester.view.physicalSize = const Size(400, 800);
tester.view.viewPadding = const FakeViewPadding(bottom: 34);
tester.view.viewInsets = FakeViewPadding(bottom: keyboard);
addTearDown(tester.view.reset);
}
void main() {
testWidgets('a moving chat tile keeps its content mounted', (tester) async {
_mounts = 0;
await tester.pumpWidget(_chatList(['a', 'b', 'c'], 0));
await tester.pump();
expect(_mounts, 3);
await tester.pumpWidget(_chatList(['c', 'a', 'b'], 1));
await tester.pump();
await tester.pumpAndSettle();
expect(_mounts, 3);
expect(
tester.getTopLeft(find.text('c')).dy,
lessThan(tester.getTopLeft(find.text('a')).dy),
);
});
testWidgets('a new notification replaces the previous one', (tester) async {
_useScreen(tester);
await tester.pumpWidget(_notificationHost());
await tester.tap(find.text('show'));
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(find.text('show'));
await tester.pump(const Duration(milliseconds: 100));
expect(find.byType(CustomNotification), findsOneWidget);
await tester.pump(const Duration(seconds: 3));
expect(find.byType(CustomNotification), findsNothing);
});
testWidgets('a notification stays above the keyboard', (tester) async {
_useScreen(tester, keyboard: 300);
await tester.pumpWidget(_notificationHost());
await tester.tap(find.text('show'));
await tester.pump(const Duration(milliseconds: 400));
final pill = tester.getRect(find.text('Синтетика'));
expect(pill.bottom, lessThan(800 - 300 - 34));
await tester.pump(const Duration(seconds: 3));
});
testWidgets('a reply thumbnail decodes at its own size', (tester) async {
final preview = ReplyPreview.of(
attachments: const [
PhotoAttachment(
baseUrl: 'https://example.test/synthetic.jpg',
width: 2000,
height: 2000,
),
],
);
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (context) => preview.thumbnail(
size: const Size(40, 40),
cs: Theme.of(context).colorScheme,
),
),
),
);
final image = tester.widget<CachedNetworkImage>(
find.byType(CachedNetworkImage),
);
expect(image.memCacheWidth, 120);
});
Widget toastHost({
required void Function(BuildContext) show,
required Widget obstruction,
}) => MaterialApp(
locale: const Locale('ru'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Builder(
builder: (context) => Scaffold(
body: Stack(
children: [
Center(
child: TextButton(
onPressed: () => show(context),
child: const Text('show'),
),
),
obstruction,
],
),
),
),
);
const fab = Positioned(
right: 20,
bottom: 110,
child: ToastObstruction(
child: SizedBox(key: ValueKey('fab'), width: 56, height: 56),
),
);
testWidgets('a toast narrows beside a side button instead of rising', (
tester,
) async {
_useScreen(tester);
await tester.pumpWidget(
toastHost(
show: (context) => showCustomNotification(context, 'Синтетика'),
obstruction: fab,
),
);
await tester.tap(find.text('show'));
await tester.pump(const Duration(milliseconds: 400));
final button = tester.getRect(find.byKey(const ValueKey('fab')));
final toast = tester.getRect(find.text('Синтетика'));
expect(toast.right, lessThan(button.left));
expect(toast.bottom, greaterThan(button.top));
await tester.pump(const Duration(seconds: 3));
});
testWidgets('a toast rises above a full-width bar', (tester) async {
_useScreen(tester);
await tester.pumpWidget(
toastHost(
show: (context) => showCustomNotification(context, 'Синтетика'),
obstruction: const Positioned(
left: 0,
right: 0,
bottom: 0,
child: ToastObstruction(
child: SizedBox(key: ValueKey('bar'), height: 160),
),
),
),
);
await tester.tap(find.text('show'));
await tester.pump(const Duration(milliseconds: 400));
final bar = tester.getRect(find.byKey(const ValueKey('bar')));
expect(
tester.getRect(find.text('Синтетика')).bottom,
lessThan(bar.top - ToastBottomPositioned.obstructionGap),
);
await tester.pump(const Duration(seconds: 3));
});
testWidgets('continue runs the pending action right away', (tester) async {
_useScreen(tester);
var committed = 0;
var undone = 0;
await tester.pumpWidget(
toastHost(
show: (context) => showUndoNotification(
context,
'Чат удалён',
onCommit: () => committed++,
onUndo: () => undone++,
),
obstruction: fab,
),
);
await tester.tap(find.text('show'));
await tester.pump(const Duration(milliseconds: 300));
expect(find.text('Продолжить'), findsOneWidget);
expect(find.text('Отменить'), findsOneWidget);
final button = tester.getRect(find.byKey(const ValueKey('fab')));
final toast = tester.getRect(
find
.ancestor(
of: find.text('Чат удалён'),
matching: find.byType(Material),
)
.first,
);
expect(toast.right, lessThan(button.left));
await tester.tap(find.text('Продолжить'));
await tester.pumpAndSettle();
expect(committed, 1);
expect(undone, 0);
expect(find.text('Чат удалён'), findsNothing);
});
}