twonly-app/test/visual/components/story_bubble_strip_test.dart
2026-10-06 22:26:53 +02:00

144 lines
4.1 KiB
Dart

import 'dart:io';
import 'package:clock/clock.dart';
import 'package:drift/native.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/stories.dao.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/visual/components/story_preview.comp.dart';
import 'package:twonly/src/visual/views/chats/chat_list_components/story_bubble_strip.comp.dart';
void main() {
late TwonlyDB database;
late Directory support;
late MediaFile media;
setUp(() async {
support = Directory.systemTemp.createTempSync('story_bubbles_test');
AppEnvironment.initTesting(customSupportDir: support.path);
database = TwonlyDB(NativeDatabase.memory());
await database.customStatement(
"INSERT INTO media_files(media_id, type) VALUES ('story', 'image')",
);
media = (await database.mediaFilesDao.getMediaFileById('story'))!;
});
tearDown(() async {
await database.close();
support.deleteSync(recursive: true);
});
StoryItem story({
required int senderId,
required String messageId,
required DateTime postedAt,
bool seen = false,
}) => StoryItem(
message: Message(
groupId: 'group-$senderId',
messageId: messageId,
senderId: senderId,
type: 'media',
mediaId: media.mediaId,
mediaStored: false,
mediaReopened: false,
isDeletedFromSender: false,
isWidgetMedia: false,
isStory: true,
openedAt: seen ? postedAt.add(const Duration(minutes: 1)) : null,
createdAt: postedAt,
),
mediaFile: media,
);
test('only active unseen contacts get a bubble', () {
final now = DateTime(2026, 10, 6, 12);
final items = buildStoryBubbleItems(
stories: [
story(
senderId: 1,
messageId: 'first-old',
postedAt: now.subtract(const Duration(hours: 2)),
),
story(
senderId: 1,
messageId: 'first-new',
postedAt: now.subtract(const Duration(hours: 1)),
),
story(
senderId: 2,
messageId: 'second',
postedAt: now.subtract(const Duration(minutes: 30)),
),
story(
senderId: 3,
messageId: 'opened',
postedAt: now.subtract(const Duration(minutes: 10)),
seen: true,
),
story(
senderId: 4,
messageId: 'expired',
postedAt: now.subtract(const Duration(hours: 25)),
),
],
labelFor: (story) => 'Contact ${story.senderId}',
now: now,
);
expect(items.map((item) => item.story.message.messageId), [
'second',
'first-old',
]);
expect(items.map((item) => item.label), ['Contact 2', 'Contact 1']);
});
testWidgets('shows a circular preview with remaining-time progress', (
tester,
) async {
final now = DateTime(2026, 10, 6, 12);
final item = StoryBubbleItem(
story: story(
senderId: 1,
messageId: 'half-life',
postedAt: now.subtract(const Duration(hours: 12)),
),
label: 'Anna',
);
StoryBubbleItem? tapped;
await withClock(Clock.fixed(now), () async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: StoryBubbleStrip(
items: [item],
onTap: (item) => tapped = item,
),
),
),
);
expect(find.text('Anna'), findsOneWidget);
expect(
tester.widget<StoryPreview>(find.byType(StoryPreview)).circle,
true,
);
final progress = tester.widget<CircularProgressIndicator>(
find.byType(CircularProgressIndicator),
);
expect(progress.value, closeTo(0.5, 0.001));
expect(
tester.getSize(find.byType(CircularProgressIndicator)),
const Size.square(64),
);
expect(progress.strokeWidth, 1.5);
await tester.tap(find.byKey(const ValueKey(1)));
expect(tapped, same(item));
await tester.pumpWidget(const SizedBox());
});
});
}