// Guards for ChainProgressBar — the rules from shared/PROGRESS.md that // are easy to break by accident when someone "simplifies" the widget. import 'package:chain_studio/widgets/chain_progress_bar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; Widget _host(Widget child, {bool disableAnimations = false}) => MaterialApp( home: Scaffold( body: MediaQuery( data: MediaQueryData(disableAnimations: disableAnimations), child: Padding(padding: const EdgeInsets.all(20), child: child), ), ), ); LinearProgressIndicator _indicator(WidgetTester tester) => tester.widget( find.byType(LinearProgressIndicator), ); void main() { testWidgets('no value renders indeterminate, never an empty bar', (tester) async { await tester.pumpWidget(_host(const ChainProgressBar(stage: 'Verbinden'))); await tester.pump(const Duration(milliseconds: 100)); expect(_indicator(tester).value, isNull, reason: 'a null value must stay indeterminate, not render as 0'); expect(find.text('Verbinden'), findsOneWidget); }); testWidgets('a value glides in and is shown as a rounded percentage', (tester) async { await tester.pumpWidget( _host(const ChainProgressBar(value: 0.42, stage: 'Herunterladen')), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 700)); expect(_indicator(tester).value, closeTo(0.42, 0.001)); expect(find.text('42 %'), findsOneWidget); }); testWidgets('the value never walks backwards', (tester) async { await tester.pumpWidget(_host(const ChainProgressBar(value: 0.6))); await tester.pump(const Duration(milliseconds: 700)); expect(_indicator(tester).value, closeTo(0.6, 0.001)); // A recomputation that comes out lower must be ignored: a bar that // jumps back destroys trust for the rest of the session. await tester.pumpWidget(_host(const ChainProgressBar(value: 0.2))); await tester.pump(const Duration(milliseconds: 700)); expect(_indicator(tester).value, closeTo(0.6, 0.001)); }); testWidgets('a standing value still animates while busy', (tester) async { await tester.pumpWidget( _host(const ChainProgressBar(value: 0.42, busy: true)), ); await tester.pump(const Duration(milliseconds: 700)); // The highlight is a repeating animation; pumpAndSettle would time // out precisely because something is still moving. That is the // property under test: the value stands, the bar does not. expect(tester.hasRunningAnimations, isTrue); }); testWidgets('nothing animates once the work is done', (tester) async { await tester.pumpWidget( _host(const ChainProgressBar(value: 1.0, busy: false)), ); await tester.pumpAndSettle(); expect(tester.hasRunningAnimations, isFalse, reason: 'an idle surface must be still'); }); testWidgets('reduce-motion drops the highlight but keeps the value', (tester) async { await tester.pumpWidget( _host( const ChainProgressBar(value: 0.5, busy: true), disableAnimations: true, ), ); await tester.pump(const Duration(milliseconds: 700)); expect(_indicator(tester).value, closeTo(0.5, 0.001)); }); testWidgets('detail text replaces the percentage when given', (tester) async { await tester.pumpWidget( _host(const ChainProgressBar( value: 0.3, stage: 'Herunterladen', detail: '3,4 von 12,0 MB', )), ); await tester.pump(const Duration(milliseconds: 700)); expect(find.text('3,4 von 12,0 MB'), findsOneWidget); expect(find.text('30 %'), findsNothing); }); testWidgets('screen readers get stage and percentage', (tester) async { final handle = tester.ensureSemantics(); await tester.pumpWidget( _host(const ChainProgressBar(value: 0.25, stage: 'Wird entpackt')), ); await tester.pump(const Duration(milliseconds: 700)); expect( tester.getSemantics(find.byType(ChainProgressBar)), matchesSemantics(label: 'Wird entpackt', value: '25 %'), ); handle.dispose(); }); }