Files
pyramid/test/live_ui_fixture_test.dart
T
Bernd SteckmeisterandClaude Opus 5.5 a1d64ec813 feat: Handy - Platz fuer Statusleiste, randlose Bild-Vollansicht mit Gesten, groessere Medien im Chat
Bernds Beta-Feedback:
- main.dart: edgeToEdge statt immersiveSticky (Statusleiste schob sich ueber
  den Inhalt); MobileShell mit SafeArea + Statusleisten-Symbolen passend
  zum Theme, Hintergrund bg1 = Kopfzeilenfarbe.
- MediaViewer: Handy randlos auf Schwarz (vorher 24-px-Rand, halbtransparent);
  Tippen neben das Bild schliesst, aufs Bild blendet Leiste ein/aus; Wischen
  hoch/runter schliesst (nur ohne Zoom, ueber InteractiveViewer-Callbacks,
  kein Gestenkonflikt mit Zwei-Finger-Zoom); Doppeltipp zoomt an der Stelle.
- Chat: Bilder auf dem Handy bis halbe Bildschirmhoehe statt fest 320 px;
  Videos im echten Seitenverhaeltnis statt fest 16:9.
- UI-Durchlauf integration_test/mobile_ui_screens_test.dart (Handy-Format,
  Testprofil, Screenshots nach build/ui_screens/) + Testinhalte-Helfer
  test/live_ui_fixture_test.dart; scripts/test.ps1 -Ui.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 16:52:10 +02:00

219 lines
6.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@TestOn('windows')
library;
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:image/image.dart' as img;
import 'package:matrix/matrix.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:sqlite3/open.dart' as sqlite_open;
/// Setup-Helfer für UI-Prüfungen (gegated wie die anderen Live-Tests, läuft
/// NIE im normalen `flutter test`): pyramidtest2 schickt pyramidtest1 eine
/// Direktnachricht mit typischem Chat-Inhalt – langer Text,
/// Antwort, Hochkant- und Querformat-Bild. Danach zeigt die GUI-Instanz mit
/// dem Testprofil (PYRAMID_PROFILE_DIR=…\profile1) echte Inhalte, an denen
/// sich Layout, Bildgrößen und Vollansicht prüfen lassen.
///
/// $env:PYRAMID_LIVE_TEST=1; flutter test test/live_ui_fixture_test.dart
///
/// Der Raum ist unverschlüsselt (der Host-Test hat kein vodozemac). Nur die
/// Skript-Geräte melden sich am Ende ab, GUI-Sessions bleiben unberührt.
Map<String, String>? _loadAccounts() {
final override = Platform.environment['PYRAMID_TEST_ACCOUNTS'];
final profile = Platform.environment['USERPROFILE'] ?? '';
final file = File(
override ?? '$profile\\.pyramid-autopilot\\test-accounts.txt',
);
if (!file.existsSync()) return null;
final map = <String, String>{};
for (final line in file.readAsLinesSync()) {
final t = line.trim();
if (t.isEmpty || t.startsWith('#')) continue;
final i = t.indexOf('=');
if (i > 0) map[t.substring(0, i)] = t.substring(i + 1);
}
return map;
}
String? _findSqliteDll() {
for (final config in ['Debug', 'Profile', 'Release']) {
final file = File('build/windows/x64/runner/$config/sqlite3.dll');
if (file.existsSync()) return file.absolute.path;
}
return null;
}
Future<Client> _login(String name, String user, String pass, Uri hs) async {
final dll = _findSqliteDll()!;
final factory = createDatabaseFactoryFfi(
ffiInit: () {
sqlite_open.open.overrideFor(
sqlite_open.OperatingSystem.windows,
() => DynamicLibrary.open(dll),
);
},
noIsolate: true,
);
final db = await factory.openDatabase(inMemoryDatabasePath);
final client = Client(
name,
database: await MatrixSdkDatabase.init(name, database: db),
);
await client.init(
waitForFirstSync: false,
waitUntilLoadCompletedLoaded: false,
);
await client.checkHomeserver(hs);
await client.login(
LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: user),
password: pass,
initialDeviceDisplayName: name,
);
return client;
}
/// Logout ohne verspätete 401 des laufenden Long-Polls (siehe
/// live_encryption_e2e_test.dart, `_logoutWithoutStraySync`).
Future<void> _logout(Client c) async {
try {
c.backgroundSync = false;
await c
.oneShotSync(timeout: Duration.zero)
.timeout(const Duration(seconds: 60));
await c.logout();
} catch (_) {}
}
/// Einfaches, gut erkennbares Testbild (Farbfläche + Kreis + Rahmen).
Uint8List _testImage(int w, int h, img.Color bg) {
final image = img.Image(width: w, height: h);
img.fill(image, color: bg);
img.fillCircle(
image,
x: w ~/ 2,
y: h ~/ 2,
radius: (w < h ? w : h) ~/ 4,
color: img.ColorRgb8(245, 166, 20),
);
img.drawRect(
image,
x1: 0,
y1: 0,
x2: w - 1,
y2: h - 1,
color: img.ColorRgb8(255, 255, 255),
thickness: 6,
);
return Uint8List.fromList(img.encodeJpg(image, quality: 85));
}
void main() {
final acc = _loadAccounts();
final skip = Platform.environment['PYRAMID_LIVE_TEST'] != '1'
? 'Live-Setup nur mit PYRAMID_LIVE_TEST=1'
: acc == null
? 'Test-Account-Datei fehlt'
: _findSqliteDll() == null
? 'SQLite-DLL fehlt (erst flutter build windows)'
: null;
test(
'UI-Testinhalte: DM pyramidtest2 → pyramidtest1 mit Text und Bildern',
() async {
final hs = Uri.parse(acc!['homeserver']!);
final c1 = await _login(
'AutopilotUiFixture1',
acc['user1']!,
acc['pass1']!,
hs,
);
final c2 = await _login(
'AutopilotUiFixture2',
acc['user2']!,
acc['pass2']!,
hs,
);
try {
await c1.oneShotSync().timeout(const Duration(seconds: 60));
await c2.oneShotSync().timeout(const Duration(seconds: 60));
final roomId = await c2.startDirectChat(
c1.userID!,
enableEncryption: false,
preset: CreateRoomPreset.privateChat,
);
final room = c2.getRoomById(roomId)!;
// user1 nimmt die Einladung an (falls noch nicht drin) – über
// room.join(), damit sie wie in der App als Direktnachricht zählt.
await c1.oneShotSync().timeout(const Duration(seconds: 60));
var r1 = c1.getRoomById(roomId);
if (r1 == null || r1.membership != Membership.join) {
if (r1 != null && r1.membership == Membership.invite) {
await r1.join();
} else {
await c1.joinRoom(roomId);
}
await c1
.waitForRoomInSync(roomId, join: true)
.timeout(const Duration(seconds: 60));
r1 = c1.getRoomById(roomId);
}
// „Chats“ in Pyramid zeigt nur Direktnachrichten (m.direct).
if (r1 != null && !r1.isDirectChat) {
await r1.addToDirectChat(c2.userID!);
}
final first = await room.sendTextEvent(
'Hallo! Das ist ein UI-Test mit einer etwas längeren Nachricht, damit '
'man sieht, wie Zeilenumbrüche, Schriftgröße und Abstände auf dem '
'Handy wirken. Umlaute: äöü ÄÖÜ ß.',
);
expect(first, isNotNull);
await c2.oneShotSync().timeout(const Duration(seconds: 30));
final firstEvent = await room.getEventById(first!);
await room.sendTextEvent(
'Kurze Antwort auf die erste Nachricht',
inReplyTo: firstEvent,
);
await room.sendFileEvent(
MatrixImageFile(
bytes: _testImage(720, 1280, img.ColorRgb8(59, 109, 17)),
name: 'hochkant.jpg',
mimeType: 'image/jpeg',
width: 720,
height: 1280,
),
);
await room.sendFileEvent(
MatrixImageFile(
bytes: _testImage(1280, 720, img.ColorRgb8(24, 95, 165)),
name: 'querformat.jpg',
mimeType: 'image/jpeg',
width: 1280,
height: 720,
),
);
await room.sendTextEvent('Und noch eine letzte Nachricht 👋');
// ignore: avoid_print
print('UI-Testraum bereit: $roomId');
} finally {
await _logout(c1);
await _logout(c2);
await c1.dispose(closeDatabase: false);
await c2.dispose(closeDatabase: false);
}
},
skip: skip,
timeout: const Timeout(Duration(minutes: 5)),
);
}