Files
sepot/app/test/avatar_test.dart
T
Julius Joska 5246a97db2 feat(app): redesigned interface, polls, drafts, forwarding and mentions
A new look across the app and a set of everyday messaging features.

Interface
- Home screen with Chats, Contacts and Settings tabs, a large title,
  search across chats and message text (diacritics-insensitive), filters
  and pinned chats.
- Conversation view with a translucent header and composer, grouped
  bubbles, author avatars and colours in groups, swipe to reply, an
  "N new messages" marker, a jump-to-latest button and in-chat search.
- Contact and group profiles: verification, shared photos, groups in
  common, disappearing messages, mute, block; members, admin badges,
  adding people, rename and leave for groups.
- Full-screen photo viewer that pages through the whole conversation.
- A stable avatar colour per person and a clearly visible read state.

Messaging
- Polls in chats and groups, single or multiple choice, with votes that
  can be changed or withdrawn; older clients see them as text.
- Drafts survive leaving a chat and show in the chat list.
- Forwarding of messages, photos, files and voice messages.
- Notes to self: a local-only, encrypted chat that never reaches the
  relay.
- @mentions in groups that notify even when the group is muted.
- Typing indicators sent as ephemeral messages, opt-in and mutual like
  read receipts.
- Delivery and read times per message and per group member.
- Profile photos, cropped, resized and stripped of metadata, delivered
  encrypted to contacts.
- Voice message playback at 1.5x and 2x.
- Call buttons in 1:1 chats and call log entries.

Fixes
- Muted groups no longer notify: muting was checked against the
  author's chat instead of the group.
- A blocked contact could unblock itself by changing its delivery
  address.
- Read receipts no longer regress to delivered when a late delivery
  receipt arrives.
- A message marked as failed corrects itself once it is delivered.
- Double ticks in groups now mean delivered to every member.
2026-10-03 22:44:53 +02:00

66 lines
2.2 KiB
Dart

import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:image/image.dart' as img;
import 'package:sepot/core/services/avatar_image.dart';
import 'package:sepot/models/avatar_ref.dart';
import 'package:sepot/models/contact.dart';
import 'package:sepot/models/identity.dart';
/// Odkaz na fotku přichází od protistrany v profilu — nesmí projít nic,
/// co by šlo zneužít (cizí cesta k blobu, klíč špatné délky).
void main() {
final ref = AvatarRef(
blobId: 'a' * 32,
key: Uint8List(32),
nonce: Uint8List(12),
digest: Uint8List(32),
);
test('drát tam a zpět', () {
final back = AvatarRef.fromWire(ref.toWire())!;
expect(back.blobId, ref.blobId);
expect(back.key, ref.key);
});
test('podvržený odkaz = null', () {
expect(AvatarRef.fromWire(null), isNull);
expect(AvatarRef.fromWire({...ref.toWire(), 'b': '../../etc/passwd'}),
isNull);
expect(AvatarRef.fromWire({...ref.toWire(), 'k': 'AAAA'}), isNull);
expect(AvatarRef.fromWire({...ref.toWire(), 'd': 'nebase64!'}), isNull);
});
test('kontakt si fotku pamatuje přes uložení', () {
final contact = Contact(
displayName: 'Alice',
publicIdentity: PublicIdentity(
displayName: 'Alice',
identityKey: Uint8List.fromList(List.filled(33, 5)),
createdAt: DateTime(2026, 9, 1),
),
addedAt: DateTime(2026, 9, 1),
avatar: ref,
);
final back = Contact.fromJson(contact.toJson());
expect(back.avatar?.blobId, ref.blobId);
});
test('fotka se ořízne na čtverec, zmenší a ztratí EXIF', () async {
final photo = img.Image(width: 800, height: 400)
..exif.imageIfd['Make'] = 'TestPhone';
final jpeg = Uint8List.fromList(img.encodeJpg(photo));
expect(img.decodeJpgExif(jpeg)?.imageIfd['Make'], isNotNull);
final out = await prepareAvatarJpeg(jpeg);
final decoded = img.decodeJpg(out!)!;
expect(decoded.width, avatarSide);
expect(decoded.height, avatarSide);
expect(img.decodeJpgExif(out)?.imageIfd['Make'], isNull);
});
test('nečitelný obrázek = null', () async {
expect(await prepareAvatarJpeg(Uint8List.fromList([1, 2, 3])), isNull);
});
}