twonly-app/test/visual/components/sticker_picker_test.dart

206 lines
6.8 KiB
Dart

import 'dart:io';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:drift/native.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/localization/generated/app_localizations.dart';
import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart';
import 'package:twonly/src/services/stickers/sticker.service.dart';
import 'package:twonly/src/visual/components/emoji_picker/emoji_picker.dart';
import 'package:twonly/src/visual/components/sticker_picker.dart';
import 'package:twonly/src/visual/elements/my_button.element.dart';
import 'package:twonly/src/visual/themes/light.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_sticker.entry.dart';
// A small valid image for exercising image widgets; protocol codec validation
// and real transparent WebP inference are covered by the Rust sticker tests.
final Uint8List _image = File(
'assets/images/default_avatar.png',
).readAsBytesSync();
StickerData _sticker() => StickerData(
version: 1,
webp: _image,
sha256: sha256.convert(_image).bytes,
width: 1,
height: 1,
);
Widget _app(Widget child) => MaterialApp(
theme: lightTheme,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
body: Align(alignment: Alignment.bottomCenter, child: child),
),
);
Future<void> _settle(WidgetTester tester) async {
await tester.runAsync(
() => Future<void>.delayed(const Duration(milliseconds: 50)),
);
await tester.pumpAndSettle();
}
void main() {
late TwonlyDB database;
setUp(() async {
SharedPreferences.setMockInitialValues({});
await locator.reset();
database = TwonlyDB(NativeDatabase.memory());
locator.registerSingleton<TwonlyDB>(database);
await database.customSelect('SELECT 1').get();
});
tearDown(() async {
await database.close();
await locator.reset();
});
testWidgets(
'tabs and resized drag handle stay in place across picker modes',
(tester) async {
var showStickers = false;
await tester.pumpWidget(
_app(
StatefulBuilder(
builder: (context, setState) {
return EmojiPicker(
alternateView: showStickers
? StickerPicker(
onEmojiPressed: () =>
setState(() => showStickers = false),
onStickerSelected: (_) {},
)
: null,
config: Config(
height: 300,
checkPlatformCompatibility: false,
bottomActionBarConfig: BottomActionBarConfig(
onStickerButtonPressed: () =>
setState(() => showStickers = true),
),
),
);
},
),
),
);
await _settle(tester);
final container = find.byKey(const Key('emojiPickerResizableContainer'));
final handle = find.byKey(const Key('emojiPickerDragHandle'));
await tester.drag(handle, const Offset(0, -80));
await _settle(tester);
final height = tester.getSize(container).height;
expect(height, greaterThan(300));
final emojiRect = tester.getRect(find.text('Emoji'));
final stickerRect = tester.getRect(find.text('Sticker'));
final handleRect = tester.getRect(handle);
await tester.tap(find.text('Sticker'));
await _settle(tester);
expect(tester.getRect(find.text('Emoji')), emojiRect);
expect(tester.getRect(find.text('Sticker')), stickerRect);
expect(tester.getRect(handle), handleRect);
expect(tester.getSize(container).height, height);
await tester.tap(find.text('Emoji'));
await _settle(tester);
expect(tester.getSize(container).height, height);
expect(tester.getRect(handle), handleRect);
},
);
testWidgets('chat tap previews without saving; buttons save then remove', (
tester,
) async {
await tester.pumpWidget(
_app(
ChatStickerEntry(
message: Message(
groupId: 'group',
messageId: 'message',
senderId: 42,
type: 'text',
mediaStored: false,
mediaReopened: false,
isDeletedFromSender: false,
isWidgetMedia: false,
isStory: false,
createdAt: DateTime.now(),
),
sticker: _sticker(),
),
),
);
await _settle(tester);
await tester.tap(find.byType(ChatStickerEntry));
await _settle(tester);
expect(find.byType(Dialog), findsOneWidget);
expect(find.text('Save sticker'), findsOneWidget);
expect(
await tester.runAsync(() => database.stickersDao.watchAll().first),
isEmpty,
);
await tester.tap(find.byType(MyButton));
await _settle(tester);
expect(find.byType(Dialog), findsNothing);
expect(
await tester.runAsync(() => database.stickersDao.watchAll().first),
hasLength(1),
);
await tester.tap(find.byType(ChatStickerEntry));
await _settle(tester);
expect(find.text('Remove from collection'), findsOneWidget);
await tester.tap(find.byType(MyButton));
await _settle(tester);
expect(
await tester.runAsync(() => database.stickersDao.watchAll().first),
isEmpty,
);
expect(find.byType(ChatStickerEntry), findsOneWidget);
});
testWidgets('long press in collection previews deletion without selecting', (
tester,
) async {
await tester.runAsync(() => StickerService.saveReceived(_sticker()));
var selected = false;
await tester.pumpWidget(
_app(
SizedBox(
height: 300,
child: StickerPicker(
onEmojiPressed: () {},
onStickerSelected: (_) => selected = true,
),
),
),
);
await _settle(tester);
final tile = find.byKey(ValueKey(sha256.convert(_image).toString()));
await tester.longPress(tile);
await _settle(tester);
expect(selected, isFalse);
expect(find.text('Delete sticker'), findsOneWidget);
expect(
await tester.runAsync(() => database.stickersDao.watchAll().first),
hasLength(1),
);
await tester.tap(find.byType(MyButton));
await _settle(tester);
expect(find.byType(Dialog), findsNothing);
expect(
await tester.runAsync(() => database.stickersDao.watchAll().first),
isEmpty,
);
expect(selected, isFalse);
// Dispose the live Drift query while the test can still drain its timers.
await tester.pumpWidget(const SizedBox.shrink());
await tester.pumpAndSettle();
});
}