// Dialog capture harness — visual proof for dialogs the page-level // guide harness doesn't reach. Renders the target dialog against // the scriptable fake hub and writes a PNG per theme via a // RepaintBoundary (driverless, headed macOS `flutter test`). // // flutter test integration_test/dialog_shots_test.dart -d macos // // Output: build/dialog-shots/-.png (or $DIALOG_SHOTS_OUT). import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:chain_studio/data/hub.dart' show StoreItem; import 'package:chain_studio/l10n/app_localizations.dart'; import 'package:chain_studio/pages/federation.dart'; import 'package:chain_studio/widgets/chain_install_confirm.dart'; import '../test/support/fake_hub.dart'; final GlobalKey _shotKey = GlobalKey(); String get _outDir => Platform.environment['DIALOG_SHOTS_OUT'] ?? 'build/dialog-shots'; Future _shot(WidgetTester tester, String name) async { await tester.pump(const Duration(milliseconds: 150)); await tester.pump(const Duration(milliseconds: 150)); final boundary = _shotKey.currentContext!.findRenderObject() as RenderRepaintBoundary; final image = await boundary.toImage(pixelRatio: 2.0); final bytes = await image.toByteData(format: ui.ImageByteFormat.png); image.dispose(); final file = File('$_outDir/$name.png'); file.parent.createSync(recursive: true); file.writeAsBytesSync(bytes!.buffer.asUint8List()); // ignore: avoid_print print('dialog-shot: ${file.path}'); } StoreItem _storeItem(String verification) => StoreItem( name: 'text.extract', taglineEn: 'Extract clean text', taglineDe: 'Reinen Text extrahieren', descriptionEn: '', descriptionDe: '', category: 'text', tags: const [], requiresCapabilities: const [], requiresServices: const [], license: 'Apache-2.0', repository: '', bestVersion: '0.1.0', status: 'alpha', installed: false, featured: false, iconUrl: '', screenshotUrls: const [], docsUrl: '', kind: 'native', provider: '', source: 'bundled', installVerification: verification, ); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); for (final (themeName, mode) in [ ('light', ThemeMode.light), ('dark', ThemeMode.dark), ]) { testWidgets('add-satellite dialog — $themeName', (tester) async { SharedPreferences.setMockInitialValues({}); installFakeHub(); await tester.pumpWidget( MaterialApp( debugShowCheckedModeBanner: false, themeMode: mode, theme: ThemeData.light(useMaterial3: true), darkTheme: ThemeData.dark(useMaterial3: true), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, locale: const Locale('de'), // Wrap the whole navigator (incl. the overlay dialogs // render into) so the capture catches the dialog, not // just the page beneath it. builder: (context, child) => RepaintBoundary(key: _shotKey, child: child), home: const FederationPage(), ), ); await tester.pump(const Duration(milliseconds: 200)); await tester.tap(find.text('Satellit hinzufügen').first); await tester.pumpAndSettle(); await _shot(tester, 'add-satellite-$themeName'); expect(find.textContaining('Ein Satellit ist ein weiterer Hub'), findsOneWidget); }); } // Trust gate with the hub-reported verification statement — the // pinned-key (good) and policy-off (warning) variants. for (final (name, verification) in [ ('trust-gate-pinned', 'pinned-key'), ('trust-gate-unverified', 'unverified'), ]) { testWidgets('$name — dark', (tester) async { SharedPreferences.setMockInitialValues({}); installFakeHub(); await tester.pumpWidget( MaterialApp( debugShowCheckedModeBanner: false, themeMode: ThemeMode.dark, theme: ThemeData.light(useMaterial3: true), darkTheme: ThemeData.dark(useMaterial3: true), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, locale: const Locale('de'), builder: (context, child) => RepaintBoundary(key: _shotKey, child: child), home: Scaffold( body: Center( child: ChainInstallConfirmDialog( item: _storeItem(verification), ), ), ), ), ); await tester.pump(const Duration(milliseconds: 200)); await _shot(tester, '$name-dark'); }); } }