Bernds Beta-Feedback: pro Raum wurde jede Nachricht durch die naechste ersetzt und auf 100 Zeichen gekuerzt, ohne Bild. Jetzt: - NotificationHelper: MessagingStyle mit den letzten 8 Nachrichten pro Raum (Verlauf in SharedPreferences, nur solange die Benachrichtigung sichtbar ist), gleiche eventId ersetzt (Platzhalter -> Klartext), nur Neues klingelt. - notification_content.dart: Text bis 1000 Zeichen, Antwort-Zitat entfernt, Medien-Labels, Vorschau-Einstellung auch bei gekillter App; Bild (E2EE entschluesselt) + Avatar per FileProvider an die System-UI. - "Gelesen" setzt eine echte Lesebestaetigung (Haupt-Engine bzw. BgEngine). - Laufende App holt genau das gepushte Event statt room.lastEvent. - Debug-only DebugNotifyReceiver zum Testen per adb ohne Login. Emulator (API 36): Verlauf, Ersetzen, Bild, Avatar, Gelesen geprueft; 9 neue Tests. Echtes Geraet (Bernd) steht aus. Co-Authored-By: Claude Opus 5.5 <[email protected]>
212 lines
5.9 KiB
Dart
212 lines
5.9 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:matrix/matrix.dart';
|
|
import 'package:pyramid/core/notification_content.dart';
|
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
|
|
|
import 'support/synthetic_matrix_data.dart';
|
|
|
|
/// Vertragstests für den Inhalt der Push-Benachrichtigungen (Bernds
|
|
/// Beta-Feedback 2026-10-07: „beim Aufklappen viel weniger Text“, „keine
|
|
/// Bildvorschau“). Native Darstellung (Verlauf, Bild) prüft das Gerät.
|
|
void main() {
|
|
late Client client;
|
|
late Room room;
|
|
|
|
setUp(() async {
|
|
final factory = createDatabaseFactoryFfi(
|
|
ffiInit: sqfliteFfiInit,
|
|
noIsolate: true,
|
|
);
|
|
final db = await factory.openDatabase(inMemoryDatabasePath);
|
|
client = await SyntheticMatrixData.openClient(db, name: 'PyramidNotifTest');
|
|
room = Room(id: '!raum:bench.invalid', client: client);
|
|
room.setState(
|
|
Event(
|
|
type: EventTypes.RoomName,
|
|
content: {'name': 'Familie'},
|
|
eventId: r'$name',
|
|
senderId: '@uta:bench.invalid',
|
|
stateKey: '',
|
|
originServerTs: DateTime(2026),
|
|
room: room,
|
|
),
|
|
);
|
|
room.setState(
|
|
Event(
|
|
type: EventTypes.RoomMember,
|
|
content: {'membership': 'join', 'displayname': 'Uta'},
|
|
eventId: r'$member',
|
|
senderId: '@uta:bench.invalid',
|
|
stateKey: '@uta:bench.invalid',
|
|
originServerTs: DateTime(2026),
|
|
room: room,
|
|
),
|
|
);
|
|
});
|
|
|
|
tearDown(() async {
|
|
await client.dispose(closeDatabase: true);
|
|
});
|
|
|
|
Event message(
|
|
Map<String, Object?> content, {
|
|
String type = EventTypes.Message,
|
|
String sender = '@uta:bench.invalid',
|
|
}) => Event(
|
|
type: type,
|
|
content: content,
|
|
eventId: r'$ev1',
|
|
senderId: sender,
|
|
originServerTs: DateTime(2026, 10, 7, 12, 30),
|
|
room: room,
|
|
);
|
|
|
|
test('Text wird NICHT mehr auf 100 Zeichen gekürzt', () {
|
|
final text = 'Wort ' * 60; // 300 Zeichen
|
|
final body = notificationBody(message({'msgtype': 'm.text', 'body': text}));
|
|
expect(body, text.trim());
|
|
});
|
|
|
|
test('sehr lange Texte werden bei 1000 Zeichen gekappt', () {
|
|
final body = notificationBody(
|
|
message({'msgtype': 'm.text', 'body': 'x' * 5000}),
|
|
)!;
|
|
expect(body.length, notificationBodyMaxChars + 1);
|
|
expect(body.endsWith('…'), isTrue);
|
|
});
|
|
|
|
test('Antwort-Zitat (Reply-Fallback) wird entfernt', () {
|
|
final body = notificationBody(
|
|
message({
|
|
'msgtype': 'm.text',
|
|
'body': '> <@bernd:bench.invalid> Kommst du heute?\n\nJa, um acht!',
|
|
}),
|
|
);
|
|
expect(body, 'Ja, um acht!');
|
|
});
|
|
|
|
test('Medien bekommen verständliche Bezeichnungen', () {
|
|
expect(
|
|
notificationBody(message({'msgtype': 'm.image', 'body': 'IMG_1.jpg'})),
|
|
'📷 Foto',
|
|
);
|
|
expect(
|
|
notificationBody(
|
|
message({
|
|
'msgtype': 'm.image',
|
|
'body': 'Unser Garten',
|
|
'filename': 'IMG_2.jpg',
|
|
}),
|
|
),
|
|
'📷 Unser Garten',
|
|
reason: 'MSC2530: abweichender body neben filename = Bildunterschrift',
|
|
);
|
|
expect(
|
|
notificationBody(message({'msgtype': 'm.video', 'body': 'v.mp4'})),
|
|
'🎥 Video',
|
|
);
|
|
expect(
|
|
notificationBody(
|
|
message({
|
|
'msgtype': 'm.audio',
|
|
'body': 'Sprachnachricht.ogg',
|
|
'org.matrix.msc3245.voice': <String, Object?>{},
|
|
}),
|
|
),
|
|
'🎤 Sprachnachricht',
|
|
);
|
|
expect(
|
|
notificationBody(message({'msgtype': 'm.audio', 'body': 'lied.mp3'})),
|
|
'🎵 Audio',
|
|
);
|
|
expect(
|
|
notificationBody(
|
|
message({
|
|
'msgtype': 'm.file',
|
|
'body': 'rechnung.pdf',
|
|
'filename': 'rechnung.pdf',
|
|
}),
|
|
),
|
|
'📎 rechnung.pdf',
|
|
);
|
|
expect(
|
|
notificationBody(message({'body': 'x'}, type: EventTypes.Sticker)),
|
|
'🏷️ Sticker',
|
|
);
|
|
});
|
|
|
|
test('nicht entschlüsselte Nachricht → null (Platzhalter bleibt)', () {
|
|
expect(
|
|
notificationBody(
|
|
message({
|
|
'algorithm': 'm.megolm.v1.aes-sha2',
|
|
'ciphertext': 'abc',
|
|
}, type: EventTypes.Encrypted),
|
|
),
|
|
isNull,
|
|
);
|
|
expect(
|
|
notificationBody(
|
|
message({
|
|
'msgtype': MessageTypes.BadEncrypted,
|
|
'body': '** Unable to decrypt: …',
|
|
}),
|
|
),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('Markdown-Fett am Anfang ist KEIN Entschlüsselungsfehler', () {
|
|
// Früher galt jeder Text mit "**" am Anfang als nicht entschlüsselt.
|
|
expect(
|
|
notificationBody(
|
|
message({'msgtype': 'm.text', 'body': '**Wichtig** heute'}),
|
|
),
|
|
'**Wichtig** heute',
|
|
);
|
|
});
|
|
|
|
test(
|
|
'Inhalt: Absender-Anzeigename, Raumname, Bild-Flag, Native-Argumente',
|
|
() {
|
|
final content = MessageNotificationContent.fromEvent(
|
|
message({'msgtype': 'm.image', 'body': 'IMG_1.jpg'}),
|
|
)!;
|
|
expect(content.senderName, 'Uta');
|
|
expect(content.roomName, 'Familie');
|
|
expect(content.isGroup, isTrue);
|
|
expect(content.isImage, isTrue);
|
|
expect(content.title, 'Uta · Familie');
|
|
|
|
final args = content.toNativeArgs(notifId: 42, imagePath: '/x/img');
|
|
expect(args['notifId'], 42);
|
|
expect(args['eventId'], r'$ev1');
|
|
expect(args['senderId'], '@uta:bench.invalid');
|
|
expect(args['imagePath'], '/x/img');
|
|
expect(
|
|
args['timestamp'],
|
|
DateTime(2026, 10, 7, 12, 30).millisecondsSinceEpoch,
|
|
);
|
|
},
|
|
);
|
|
|
|
test('unbekannter Absender → Localpart statt leerem Namen', () {
|
|
final content = MessageNotificationContent.fromEvent(
|
|
message({
|
|
'msgtype': 'm.text',
|
|
'body': 'Hi',
|
|
}, sender: '@jochen:bench.invalid'),
|
|
)!;
|
|
expect(content.senderName, 'jochen');
|
|
});
|
|
|
|
test('Vorschau aus → kein Inhalt, kein Bild', () {
|
|
final content = MessageNotificationContent.fromEvent(
|
|
message({'msgtype': 'm.image', 'body': 'IMG_1.jpg'}),
|
|
showPreview: false,
|
|
)!;
|
|
expect(content.body, notificationPlaceholderBody);
|
|
expect(content.isImage, isFalse);
|
|
});
|
|
}
|