mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
154 lines
4.7 KiB
Dart
154 lines
4.7 KiB
Dart
/*
|
|
* Modified version of https://github.com/Fintasys/emoji_picker_flutter
|
|
* MIT License
|
|
* Copyright (c) 2024 Stefan Humm
|
|
*/
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:twonly/src/visual/components/emoji_picker/emoji_picker.dart';
|
|
import 'package:twonly/src/visual/components/emoji_picker/emoji_picker_internal_utils.dart';
|
|
|
|
/// Test harness that lets us trigger a rebuild of [EmojiPicker] from its
|
|
/// parent, mimicking an unrelated `setState()` higher up in the widget tree.
|
|
class _RebuildHarness extends StatefulWidget {
|
|
const _RebuildHarness({
|
|
required this.pickerKey,
|
|
required this.config,
|
|
super.key,
|
|
});
|
|
|
|
final GlobalKey<EmojiPickerState> pickerKey;
|
|
final Config config;
|
|
|
|
@override
|
|
State<_RebuildHarness> createState() => _RebuildHarnessState();
|
|
}
|
|
|
|
class _RebuildHarnessState extends State<_RebuildHarness> {
|
|
int _rebuildCounter = 0;
|
|
|
|
/// Force the parent to rebuild without touching the picker's own state.
|
|
void forceRebuild() => setState(() => _rebuildCounter++);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
// Reference the counter so the framework actually rebuilds the subtree.
|
|
body: Column(
|
|
children: [
|
|
Text('rebuilds: $_rebuildCounter'),
|
|
Expanded(
|
|
child: EmojiPicker(
|
|
key: widget.pickerKey,
|
|
config: widget.config,
|
|
onEmojiSelected: (_, _) {},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
const config = Config(
|
|
height: 400,
|
|
);
|
|
|
|
// Deliberately not part of the built-in set. In the continuous picker a
|
|
// normal emoji can be visible both in Recents and in its original category.
|
|
const emoji = Emoji('recent-only', 'recent-only');
|
|
|
|
setUp(() {
|
|
// Start every test with an empty recent-emoji store.
|
|
SharedPreferences.setMockInitialValues({});
|
|
// The internal caches are static and would otherwise leak the recent
|
|
// emoji added by one test into the next.
|
|
EmojiPickerInternalUtils.resetCaches();
|
|
});
|
|
|
|
testWidgets(
|
|
'A recent update with refresh:false must not leak into the UI on an '
|
|
'unrelated parent rebuild',
|
|
(tester) async {
|
|
final pickerKey = GlobalKey<EmojiPickerState>();
|
|
final harnessKey = GlobalKey<_RebuildHarnessState>();
|
|
|
|
await tester.pumpWidget(
|
|
_RebuildHarness(key: harnessKey, pickerKey: pickerKey, config: config),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Recent tab starts empty.
|
|
expect(find.text('No Recents'), findsOneWidget);
|
|
expect(find.text(emoji.emoji), findsNothing);
|
|
|
|
// Persist a recent emoji through the public API. This calls
|
|
// `updateRecentEmoji(..., refresh: false)` under the hood - the same
|
|
// path taken when an emoji is tapped while the RECENT tab is visible.
|
|
await EmojiPickerUtils().addEmojiToRecentlyUsed(
|
|
key: pickerKey,
|
|
emoji: emoji,
|
|
config: config,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// refresh:false means the visible list must NOT change yet.
|
|
expect(
|
|
find.text('No Recents'),
|
|
findsOneWidget,
|
|
reason: 'refresh:false should not update the UI immediately',
|
|
);
|
|
expect(find.text(emoji.emoji), findsNothing);
|
|
|
|
// Now an unrelated part of the app rebuilds the widget tree.
|
|
harnessKey.currentState!.forceRebuild();
|
|
await tester.pumpAndSettle();
|
|
|
|
// The recent list must STILL be unchanged: a refresh:false update is not
|
|
// allowed to surface on a stray rebuild triggered from a parent widget.
|
|
expect(
|
|
find.text('No Recents'),
|
|
findsOneWidget,
|
|
reason: 'a refresh:false update must not leak in on a parent rebuild',
|
|
);
|
|
expect(find.text(emoji.emoji), findsNothing);
|
|
},
|
|
);
|
|
|
|
testWidgets('A recent update with refresh:true updates the UI immediately', (
|
|
tester,
|
|
) async {
|
|
final pickerKey = GlobalKey<EmojiPickerState>();
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: EmojiPicker(
|
|
key: pickerKey,
|
|
config: config,
|
|
onEmojiSelected: (_, _) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('No Recents'), findsOneWidget);
|
|
|
|
// An explicit refresh must render the new recent emoji right away.
|
|
pickerKey.currentState!.updateRecentEmoji([
|
|
RecentEmoji(emoji, 1),
|
|
], refresh: true);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('No Recents'), findsNothing);
|
|
expect(find.text(emoji.emoji), findsOneWidget);
|
|
});
|
|
}
|