feat(studio): real install progress in the store dialog and the wizard
Both surfaces showed an indeterminate spinner for the whole install, because the hub only offered a unary call. They now follow HubAdmin/InstallModuleStream: - The store's install dialog shows the phase in plain language, the overall percentage, megabytes while the size is known, and a seconds counter. The counter is the second, independent signal: it keeps running even if the hub goes quiet, which is what tells 'slow' apart from 'stuck'. - The setup wizard shows the same bar per module instead of a spinner inside the button. - New strings in both locales for the phases, the byte line and the elapsed counter. progress_bar_visual_test writes the four waiting states to build/progress/ in either theme, so the result can be judged without launching the desktop app. Writing it turned up a real trap worth recording: awaiting toImage() directly in a widget test leaves the binding waiting forever - the file passed in six seconds, then sat there until the ten-minute timeout failed the whole suite. tester.runAsync() is the fix. Full suite: 186 passed. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
2bc556e0f5
commit
1549334767
9 changed files with 490 additions and 74 deletions
124
test/progress_bar_visual_test.dart
Normal file
124
test/progress_bar_visual_test.dart
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// Renders the progress bar in the states an operator actually meets
|
||||
// and writes PNGs to build/progress/, so the visual result can be
|
||||
// judged without launching the desktop app and stealing focus.
|
||||
//
|
||||
// Not a golden comparison: there is no committed reference to diff
|
||||
// against. It exists to produce evidence, and to guard that every one
|
||||
// of these states renders at all in both themes.
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:chain_studio/theme/theme.dart';
|
||||
import 'package:chain_studio/widgets/chain_progress_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class _Case {
|
||||
final String label;
|
||||
final Widget bar;
|
||||
const _Case(this.label, this.bar);
|
||||
}
|
||||
|
||||
const _cases = <_Case>[
|
||||
_Case(
|
||||
'Nichts messbar: unbestimmt statt leer',
|
||||
ChainProgressBar(stage: 'Modul wird gesucht'),
|
||||
),
|
||||
_Case(
|
||||
'Download mit Groesse',
|
||||
ChainProgressBar(
|
||||
value: 0.42,
|
||||
stage: 'Wird heruntergeladen',
|
||||
detail: '5,0 von 12,0 MB',
|
||||
),
|
||||
),
|
||||
_Case(
|
||||
'Wert steht, Arbeit laeuft weiter',
|
||||
ChainProgressBar(value: 0.74, stage: 'Wird geprueft und entpackt'),
|
||||
),
|
||||
_Case(
|
||||
'Fertig: nichts bewegt sich mehr',
|
||||
ChainProgressBar(value: 1.0, busy: false, stage: 'Fertig'),
|
||||
),
|
||||
];
|
||||
|
||||
Widget _sheet(ThemeData theme, String title) => MaterialApp(
|
||||
theme: theme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: Scaffold(
|
||||
body: RepaintBoundary(
|
||||
key: const ValueKey('sheet'),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 520,
|
||||
child: Card(
|
||||
margin: const EdgeInsets.all(24),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(title, style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 20),
|
||||
for (final c in _cases) ...[
|
||||
Text(c.label, style: theme.textTheme.labelSmall),
|
||||
const SizedBox(height: 6),
|
||||
c.bar,
|
||||
const SizedBox(height: 22),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _capture(WidgetTester tester, String name) async {
|
||||
final boundary = tester.renderObject<RenderRepaintBoundary>(
|
||||
find.byKey(const ValueKey('sheet')),
|
||||
);
|
||||
// toImage is real asynchronous engine work. Awaiting it directly in a
|
||||
// widget test leaves the binding waiting forever afterwards, which
|
||||
// showed up as this file passing in six seconds and then sitting
|
||||
// there until the ten-minute timeout killed the whole suite.
|
||||
final image = await tester.runAsync(() => boundary.toImage(pixelRatio: 2));
|
||||
if (image == null) return;
|
||||
final bytes = await tester.runAsync(
|
||||
() => image.toByteData(format: ui.ImageByteFormat.png),
|
||||
);
|
||||
final dir = Directory('build/progress')..createSync(recursive: true);
|
||||
File('${dir.path}/$name.png').writeAsBytesSync(bytes!.buffer.asUint8List());
|
||||
}
|
||||
|
||||
void main() {
|
||||
// One capture per run: a second toImage() in the same test binding
|
||||
// never completes, so the theme comes from the environment and the
|
||||
// sheet is rendered twice from outside:
|
||||
//
|
||||
// flutter test test/progress_bar_visual_test.dart
|
||||
// PROGRESS_THEME=dark flutter test test/progress_bar_visual_test.dart
|
||||
testWidgets('renders every waiting state', (tester) async {
|
||||
final dark = Platform.environment['PROGRESS_THEME'] == 'dark';
|
||||
tester.view.physicalSize = const Size(1100, 900);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.reset);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_sheet(
|
||||
dark ? ChainTheme.dark() : ChainTheme.light(),
|
||||
dark ? 'Dunkel' : 'Hell',
|
||||
),
|
||||
);
|
||||
// Mid-sweep, so the highlight sits inside the bar in the capture.
|
||||
await tester.pump(const Duration(milliseconds: 900));
|
||||
expect(find.byType(ChainProgressBar), findsNWidgets(_cases.length));
|
||||
await _capture(tester, dark ? 'states-dark' : 'states-light');
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue