// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import '../rendering/mock_canvas.dart'; void main() { // The "can be constructed" tests that follow are primarily to ensure that any // animations started by the progress indicators are stopped at dispose() time. testWidgets('LinearProgressIndicator(value: 0.0) can be constructed and has empty semantics by default', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(value: 0.0), ), ), ), ); expect(tester.getSemantics(find.byType(LinearProgressIndicator)), matchesSemantics()); handle.dispose(); }); testWidgets('LinearProgressIndicator(value: null) can be constructed and has empty semantics by default', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( const Directionality( textDirection: TextDirection.rtl, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(value: null), ), ), ), ); expect(tester.getSemantics(find.byType(LinearProgressIndicator)), matchesSemantics()); handle.dispose(); }); testWidgets('LinearProgressIndicator custom minHeight', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(value: 0.25, minHeight: 2.0), ), ), ), ); expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 2.0)) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 50.0, 2.0)), ); }); testWidgets('LinearProgressIndicator paint (LTR)', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(value: 0.25), ), ), ), ); expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 4.0)) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 50.0, 4.0)), ); expect(tester.binding.transientCallbackCount, 0); }); testWidgets('LinearProgressIndicator paint (RTL)', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.rtl, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(value: 0.25), ), ), ), ); expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 4.0)) ..rect(rect: const Rect.fromLTRB(150.0, 0.0, 200.0, 4.0)), ); expect(tester.binding.transientCallbackCount, 0); }); testWidgets('LinearProgressIndicator indeterminate (LTR)', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(), ), ), ), ); await tester.pump(const Duration(milliseconds: 300)); final double animationValue = const Interval(0.0, 750.0 / 1800.0, curve: Cubic(0.2, 0.0, 0.8, 1.0)) .transform(300.0 / 1800.0); expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 4.0)) ..rect(rect: Rect.fromLTRB(0.0, 0.0, animationValue * 200.0, 4.0)), ); expect(tester.binding.transientCallbackCount, 1); }); testWidgets('LinearProgressIndicator paint (RTL)', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.rtl, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(), ), ), ), ); await tester.pump(const Duration(milliseconds: 300)); final double animationValue = const Interval(0.0, 750.0 / 1800.0, curve: Cubic(0.2, 0.0, 0.8, 1.0)) .transform(300.0 / 1800.0); expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 4.0)) ..rect(rect: Rect.fromLTRB(200.0 - animationValue * 200.0, 0.0, 200.0, 4.0)), ); expect(tester.binding.transientCallbackCount, 1); }); testWidgets('LinearProgressIndicator with colors', (WidgetTester tester) async { // With valueColor & color provided await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator( value: 0.25, backgroundColor: Colors.black, color: Colors.blue, valueColor: AlwaysStoppedAnimation<Color>(Colors.white), ), ), ), ), ); // Should use valueColor expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 4.0)) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 50.0, 4.0), color: Colors.white), ); // With just color provided await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator( value: 0.25, backgroundColor: Colors.black, color: Colors.white12, ), ), ), ), ); // Should use color expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 4.0)) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 50.0, 4.0), color: Colors.white12), ); // With no color provided const Color primaryColor = Color(0xff008800); final ThemeData theme = ThemeData(colorScheme: ColorScheme.fromSwatch().copyWith(primary: primaryColor)); await tester.pumpWidget( Theme( data: theme, child: const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator( value: 0.25, backgroundColor: Colors.black, ), ), ), ), ), ); // Should use the theme's primary color expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 4.0)) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 50.0, 4.0), color: primaryColor), ); }); testWidgets('LinearProgressIndicator with animation with null colors', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator( value: 0.25, valueColor: AlwaysStoppedAnimation<Color?>(null), backgroundColor: Colors.black, ), ), ), ), ); expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 4.0)) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 50.0, 4.0)), ); }); testWidgets('CircularProgressIndicator(value: 0.0) can be constructed and has value semantics by default', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: CircularProgressIndicator(value: 0.0), ), ), ); expect(tester.getSemantics(find.byType(CircularProgressIndicator)), matchesSemantics( value: '0%', textDirection: TextDirection.ltr, )); handle.dispose(); }); testWidgets('CircularProgressIndicator(value: null) can be constructed and has empty semantics by default', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( const Center( child: CircularProgressIndicator(value: null), ), ); expect(tester.getSemantics(find.byType(CircularProgressIndicator)), matchesSemantics()); handle.dispose(); }); testWidgets('LinearProgressIndicator causes a repaint when it changes', (WidgetTester tester) async { await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ListView(children: const <Widget>[LinearProgressIndicator(value: 0.0)]), )); final List<Layer> layers1 = tester.layers; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ListView(children: const <Widget>[LinearProgressIndicator(value: 0.5)])), ); final List<Layer> layers2 = tester.layers; expect(layers1, isNot(equals(layers2))); }); testWidgets('CircularProgressIndicator stoke width', (WidgetTester tester) async { await tester.pumpWidget(const CircularProgressIndicator()); expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 4.0)); await tester.pumpWidget(const CircularProgressIndicator(strokeWidth: 16.0)); expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 16.0)); }); testWidgets('CircularProgressIndicator paint colors', (WidgetTester tester) async { const Color green = Color(0xFF00FF00); const Color blue = Color(0xFF0000FF); const Color red = Color(0xFFFF0000); // With valueColor & color provided await tester.pumpWidget(const CircularProgressIndicator( color: red, valueColor: AlwaysStoppedAnimation<Color>(blue), )); expect(find.byType(CircularProgressIndicator), paintsExactlyCountTimes(#drawArc, 1)); expect(find.byType(CircularProgressIndicator), paints..arc(color: blue)); // With just color provided await tester.pumpWidget(const CircularProgressIndicator( color: red, )); expect(find.byType(CircularProgressIndicator), paintsExactlyCountTimes(#drawArc, 1)); expect(find.byType(CircularProgressIndicator), paints..arc(color: red)); // With no color provided await tester.pumpWidget(Theme( data: ThemeData(colorScheme: ColorScheme.fromSwatch().copyWith(primary: green)), child: const CircularProgressIndicator(), )); expect(find.byType(CircularProgressIndicator), paintsExactlyCountTimes(#drawArc, 1)); expect(find.byType(CircularProgressIndicator), paints..arc(color: green)); // With background await tester.pumpWidget(const CircularProgressIndicator( backgroundColor: green, color: blue, )); expect(find.byType(CircularProgressIndicator), paintsExactlyCountTimes(#drawArc, 2)); expect(find.byType(CircularProgressIndicator), paints..arc(color: green)..arc(color: blue)); }); testWidgets('Indeterminate RefreshProgressIndicator keeps spinning until end of time (approximate)', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/13782 await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: RefreshProgressIndicator(), ), ), ), ); expect(tester.hasRunningAnimations, isTrue); await tester.pump(const Duration(seconds: 5)); expect(tester.hasRunningAnimations, isTrue); await tester.pump(const Duration(milliseconds: 1)); expect(tester.hasRunningAnimations, isTrue); await tester.pump(const Duration(days: 9999)); expect(tester.hasRunningAnimations, isTrue); }); testWidgets('Determinate CircularProgressIndicator stops the animator', (WidgetTester tester) async { double? progressValue; late StateSetter setState; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Center( child: StatefulBuilder( builder: (BuildContext context, StateSetter setter) { setState = setter; return CircularProgressIndicator(value: progressValue); } ), ), ), ); expect(tester.hasRunningAnimations, isTrue); setState(() { progressValue = 1.0; }); await tester.pump(const Duration(milliseconds: 1)); expect(tester.hasRunningAnimations, isFalse); setState(() { progressValue = null; }); await tester.pump(const Duration(milliseconds: 1)); expect(tester.hasRunningAnimations, isTrue); }); testWidgets('LinearProgressIndicator with height 12.0', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 100.0, height: 12.0, child: LinearProgressIndicator(value: 0.25), ), ), ), ); expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 100.0, 12.0)) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 25.0, 12.0)), ); expect(tester.binding.transientCallbackCount, 0); }); testWidgets('LinearProgressIndicator with a height less than the minimum', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 100.0, height: 3.0, child: LinearProgressIndicator(value: 0.25), ), ), ), ); expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 100.0, 3.0)) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 25.0, 3.0)), ); expect(tester.binding.transientCallbackCount, 0); }); testWidgets('LinearProgressIndicator with default height', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 100.0, height: 4.0, child: LinearProgressIndicator(value: 0.25), ), ), ), ); expect( find.byType(LinearProgressIndicator), paints ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 100.0, 4.0)) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 25.0, 4.0)), ); expect(tester.binding.transientCallbackCount, 0); }); testWidgets('LinearProgressIndicator can be made accessible', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); final GlobalKey key = GlobalKey(); const String label = 'Label'; const String value = '25%'; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: LinearProgressIndicator( key: key, value: 0.25, semanticsLabel: label, semanticsValue: value, ), ), ); expect(tester.getSemantics(find.byKey(key)), matchesSemantics( textDirection: TextDirection.ltr, label: label, value: value, )); handle.dispose(); }); testWidgets('LinearProgressIndicator that is determinate gets default a11y value', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); final GlobalKey key = GlobalKey(); const String label = 'Label'; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: LinearProgressIndicator( key: key, value: 0.25, semanticsLabel: label, ), ), ); expect(tester.getSemantics(find.byKey(key)), matchesSemantics( textDirection: TextDirection.ltr, label: label, value: '25%', )); handle.dispose(); }); testWidgets('LinearProgressIndicator that is determinate does not default a11y value when label is null', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: LinearProgressIndicator( key: key, value: 0.25, ), ), ); expect(tester.getSemantics(find.byKey(key)), matchesSemantics()); handle.dispose(); }); testWidgets('LinearProgressIndicator that is indeterminate does not default a11y value', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); final GlobalKey key = GlobalKey(); const String label = 'Progress'; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: LinearProgressIndicator( key: key, value: 0.25, semanticsLabel: label, ), ), ); expect(tester.getSemantics(find.byKey(key)), matchesSemantics( textDirection: TextDirection.ltr, label: label, )); handle.dispose(); }); testWidgets('CircularProgressIndicator can be made accessible', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); final GlobalKey key = GlobalKey(); const String label = 'Label'; const String value = '25%'; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: CircularProgressIndicator( key: key, value: 0.25, semanticsLabel: label, semanticsValue: value, ), ), ); expect(tester.getSemantics(find.byKey(key)), matchesSemantics( textDirection: TextDirection.ltr, label: label, value: value, )); handle.dispose(); }); testWidgets('RefreshProgressIndicator can be made accessible', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); final GlobalKey key = GlobalKey(); const String label = 'Label'; const String value = '25%'; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: RefreshProgressIndicator( key: key, semanticsLabel: label, semanticsValue: value, ), ), ); expect(tester.getSemantics(find.byKey(key)), matchesSemantics( textDirection: TextDirection.ltr, label: label, value: value, )); handle.dispose(); }); testWidgets('Indeterminate CircularProgressIndicator uses expected animation', (WidgetTester tester) async { final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(40, 40)); await tester.pumpFrames(animationSheet.record( const Directionality( textDirection: TextDirection.ltr, child: Padding( padding: EdgeInsets.all(4), child: CircularProgressIndicator(), ), ), ), const Duration(seconds: 2)); tester.binding.setSurfaceSize(animationSheet.sheetSize()); final Widget display = await animationSheet.display(); await tester.pumpWidget(display); await expectLater( find.byWidget(display), matchesGoldenFile('material.circular_progress_indicator.indeterminate.png'), ); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/42767 testWidgets( 'Adaptive CircularProgressIndicator displays CupertinoActivityIndicator in iOS', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Scaffold( body: Material( child: CircularProgressIndicator.adaptive(), ), ), ), ); expect(find.byType(CupertinoActivityIndicator), findsOneWidget); }, variant: const TargetPlatformVariant(<TargetPlatform> { TargetPlatform.iOS, TargetPlatform.macOS, }) ); testWidgets( 'Adaptive CircularProgressIndicator does not display CupertinoActivityIndicator in non-iOS', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Scaffold( body: Material( child: CircularProgressIndicator.adaptive(), ), ), ), ); expect(find.byType(CupertinoActivityIndicator), findsNothing); }, variant: const TargetPlatformVariant(<TargetPlatform> { TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.windows, TargetPlatform.linux, }), ); }