chain-studio/integration_test/dialog_shots_test.dart
flemming-it 2f076ccf29
Some checks failed
Security / Security check (push) Failing after 1s
feat(store,doctor): surface the hub's trust + exposure data (0.77.0)
The trust-gate dialog replaces its generic 'no per-entry status yet
(alpha)' note with the hub's classified verification statement —
pinned store key / trusted publishers (green), installs without
signature checking (amber), install would be refused (red), bridge
entry (neutral) — via one shared describeInstallVerification mapping.
Against a pre-0.23 hub the field is empty and the old honest wording
stays (pinned by test).

Information architecture: policy-off is a GLOBAL fact, so it appears
as ONE ChainInlineHelp notice above the store grid instead of a
warning pill on every card (card noise); only 'blocked' — a genuine
per-source anomaly — earns a card pill. Doctor's host services show
the hub-classified network reach per endpoint (local only / private
network / publicly reachable with a protect-it hint / reach unknown).

Verified end-to-end against the live dev hub (guide harness): the
wire field arrives, the store page shows exactly one policy notice
and quiet cards; trust-gate variants captured via the dialog
harness. Suite 123 green.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-07-21 13:14:13 +02:00

140 lines
4.8 KiB
Dart

// 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/<name>-<theme>.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<void> _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');
});
}
}