Some checks failed
Security / Security check (push) Failing after 2s
The detail sheet always answers 'who maintains this?': one selectable line per maintainer from the store index's new maintainers list, or an honest 'not specified' when the index names nobody (StoreMaintainersSection, public for the widget tests). The install trust gate carries the same fact when present and stays terse otherwise. DE+EN; dialog-harness proof captured with the maintainer row. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
203 lines
7.4 KiB
Dart
203 lines
7.4 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_flow_editor/src/l10n.dart';
|
|
import 'package:chain_studio_flow_editor/src/widgets/missing_modules_badge.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,
|
|
maintainers: const ['Dr. Stefan Flemming (Flemming.AI) <chain@flemming.ai>'],
|
|
);
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
// Flow-list resolvability badge, both classified states side by
|
|
// side: store-resolvable missing capability (amber chip +
|
|
// Install link) vs. capability no store can install (quiet
|
|
// "nicht im Store" chip; recovery paths live in its tooltip).
|
|
for (final (themeName, mode) in [
|
|
('light', ThemeMode.light),
|
|
('dark', ThemeMode.dark),
|
|
]) {
|
|
testWidgets('flow-list badge states — $themeName', (tester) async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
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'),
|
|
builder: (context, child) =>
|
|
RepaintBoundary(key: _shotKey, child: child),
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 260),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
MissingModulesBadge(
|
|
installable: const ['text.extract'],
|
|
notInStore: const [],
|
|
strings:
|
|
const FlowEditorStrings(FlowEditorLocale.de),
|
|
onInstall: () {},
|
|
),
|
|
const SizedBox(height: 16),
|
|
MissingModulesBadge(
|
|
installable: const [],
|
|
notInStore: const ['acme.internal/lookup'],
|
|
strings:
|
|
const FlowEditorStrings(FlowEditorLocale.de),
|
|
onInstall: null,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
await _shot(tester, 'flow-badge-states-$themeName');
|
|
expect(find.text('1 Modul fehlt'), findsOneWidget);
|
|
expect(find.text('Installieren'), findsOneWidget);
|
|
expect(find.text('nicht im Store'), 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');
|
|
});
|
|
}
|
|
}
|