// 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. // no-shuffle: // //TODO(gspencergoog): Remove this tag once this test's state leaks/test // dependencies have been fixed. // https://github.com/flutter/flutter/issues/85160 // Fails with "flutter test --test-randomize-ordering-seed=456" // reduced-test-set: // This file is run as part of a reduced test set in CI on Mac and Windows // machines. @Tags(['reduced-test-set', 'no-shuffle']) library; import 'dart:ui' as ui; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { final theme = ThemeData(); // 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( Theme( data: theme, child: 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( Theme( data: theme, child: const Directionality( textDirection: TextDirection.rtl, child: Center(child: SizedBox(width: 200.0, child: LinearProgressIndicator())), ), ), ); expect(tester.getSemantics(find.byType(LinearProgressIndicator)), matchesSemantics()); handle.dispose(); }, ); testWidgets('LinearProgressIndicator custom minHeight', (WidgetTester tester) async { await tester.pumpWidget( Theme( data: theme, child: 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)), ); // Same test, but using the theme await tester.pumpWidget( Theme( data: theme.copyWith( progressIndicatorTheme: const ProgressIndicatorThemeData(linearMinHeight: 2.0), ), child: 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, 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( Theme( data: theme, child: 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( Theme( data: theme, child: 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( Theme( data: theme, child: 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: Rect.fromLTRB(animationValue * 200.0, 0.0, 200.0, 4.0)) // Track ..rect(rect: Rect.fromLTRB(0.0, 0.0, animationValue * 200.0, 4.0)), // Active indicator ); expect(tester.binding.transientCallbackCount, 1); }); testWidgets('LinearProgressIndicator paint (RTL)', (WidgetTester tester) async { await tester.pumpWidget( Theme( data: theme, child: 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: Rect.fromLTRB(0.0, 0.0, 200.0 - animationValue * 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 { await tester.pumpWidget( const MaterialApp( home: Center(child: SizedBox(width: 200.0, child: LinearProgressIndicator(value: 0.25))), ), ); // Defaults. expect( find.byType(LinearProgressIndicator), paints ..rect( rect: const Rect.fromLTRB(0.0, 0.0, 200.0, 4.0), color: theme.colorScheme.secondaryContainer, ) ..rect(rect: const Rect.fromLTRB(0.0, 0.0, 50.0, 4.0), color: theme.colorScheme.primary), ); // With valueColor & color provided 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, color: Colors.blue, valueColor: AlwaysStoppedAnimation(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( Theme( data: theme, child: 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 primaryColor = Color(0xff008800); await tester.pumpWidget( Theme( data: theme.copyWith(colorScheme: ColorScheme.fromSwatch().copyWith(primary: primaryColor)), 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), ); // With ProgressIndicatorTheme colors const indicatorColor = Color(0xff0000ff); await tester.pumpWidget( Theme( data: theme.copyWith( progressIndicatorTheme: const ProgressIndicatorThemeData( color: indicatorColor, linearTrackColor: Colors.black, ), ), child: const Directionality( textDirection: TextDirection.ltr, child: Center(child: SizedBox(width: 200.0, child: LinearProgressIndicator(value: 0.25))), ), ), ); // Should use the progress indicator theme colors 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: indicatorColor), ); }); testWidgets('LinearProgressIndicator with animation with null colors', ( WidgetTester tester, ) async { await tester.pumpWidget( Theme( data: theme, child: const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator( value: 0.25, valueColor: AlwaysStoppedAnimation(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( Theme( data: theme, child: const Directionality( textDirection: TextDirection.ltr, child: Center(child: CircularProgressIndicator(value: 0.0)), ), ), ); expect( tester.getSemantics(find.byType(CircularProgressIndicator)), matchesSemantics( value: '0', textDirection: TextDirection.ltr, minValue: '0', maxValue: '100', ), ); 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( Theme( data: theme, child: const Center(child: CircularProgressIndicator()), ), ); expect(tester.getSemantics(find.byType(CircularProgressIndicator)), matchesSemantics()); handle.dispose(); }, ); testWidgets('LinearProgressIndicator causes a repaint when it changes', ( WidgetTester tester, ) async { await tester.pumpWidget( Theme( data: theme, child: Directionality( textDirection: TextDirection.ltr, child: ListView(children: const [LinearProgressIndicator(value: 0.0)]), ), ), ); final List layers1 = tester.layers; await tester.pumpWidget( Theme( data: theme, child: Directionality( textDirection: TextDirection.ltr, child: ListView(children: const [LinearProgressIndicator(value: 0.5)]), ), ), ); final List layers2 = tester.layers; expect(layers1, isNot(equals(layers2))); }); testWidgets('CircularProgressIndicator stroke width', (WidgetTester tester) async { await tester.pumpWidget(Theme(data: theme, child: const CircularProgressIndicator())); expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 4.0)); await tester.pumpWidget( Theme(data: theme, child: const CircularProgressIndicator(strokeWidth: 16.0)), ); expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 16.0)); }); testWidgets('CircularProgressIndicator.adaptive stroke width', (WidgetTester tester) async { await tester.pumpWidget(const CircularProgressIndicator.adaptive()); // The default strokeWidth is 4.0 expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 4.0)); final ThemeData themeData = theme.copyWith( progressIndicatorTheme: const ProgressIndicatorThemeData(strokeWidth: 10.0), ); await tester.pumpWidget( Theme(data: themeData, child: const CircularProgressIndicator.adaptive()), ); // Get the theme’s strokeWidth. expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 10.0)); await tester.pumpWidget( Theme(data: themeData, child: const CircularProgressIndicator.adaptive(strokeWidth: 16.0)), ); // The strokeWidth parameter should override the theme’s strokeWidth. expect(find.byType(CircularProgressIndicator), paints..arc(strokeWidth: 16.0)); }); testWidgets('CircularProgressIndicator strokeAlign', (WidgetTester tester) async { await tester.pumpWidget(Theme(data: theme, child: const CircularProgressIndicator())); expect( find.byType(CircularProgressIndicator), paints..arc(rect: Offset.zero & const Size(800.0, 600.0)), ); await tester.pumpWidget( Theme( data: theme, child: const CircularProgressIndicator( strokeAlign: CircularProgressIndicator.strokeAlignInside, ), ), ); expect( find.byType(CircularProgressIndicator), paints..arc(rect: const Offset(2.0, 2.0) & const Size(796.0, 596.0)), ); await tester.pumpWidget( Theme( data: theme, child: const CircularProgressIndicator( strokeAlign: CircularProgressIndicator.strokeAlignOutside, ), ), ); expect( find.byType(CircularProgressIndicator), paints..arc(rect: const Offset(-2.0, -2.0) & const Size(804.0, 604.0)), ); // Unbounded alignment. await tester.pumpWidget( Theme(data: theme, child: const CircularProgressIndicator(strokeAlign: 2.0)), ); expect( find.byType(CircularProgressIndicator), paints..arc(rect: const Offset(-4.0, -4.0) & const Size(808.0, 608.0)), ); }); testWidgets('CircularProgressIndicator with strokeCap', (WidgetTester tester) async { await tester.pumpWidget(const CircularProgressIndicator()); expect( find.byType(CircularProgressIndicator), paints..arc(strokeCap: StrokeCap.square), reason: 'Default indeterminate strokeCap is StrokeCap.square.', ); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: CircularProgressIndicator(value: 0.5), ), ); expect( find.byType(CircularProgressIndicator), paints..arc(strokeCap: StrokeCap.butt), reason: 'Default determinate strokeCap is StrokeCap.butt.', ); await tester.pumpWidget(const CircularProgressIndicator(strokeCap: StrokeCap.butt)); expect( find.byType(CircularProgressIndicator), paints..arc(strokeCap: StrokeCap.butt), reason: 'strokeCap can be set to StrokeCap.butt, and will not be overridden.', ); await tester.pumpWidget(const CircularProgressIndicator(strokeCap: StrokeCap.round)); expect(find.byType(CircularProgressIndicator), paints..arc(strokeCap: StrokeCap.round)); }); testWidgets('LinearProgressIndicator with indicatorBorderRadius', (WidgetTester tester) async { await tester.pumpWidget( Theme( data: theme, child: const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 100.0, height: 4.0, child: LinearProgressIndicator( value: 0.25, borderRadius: BorderRadius.all(Radius.circular(10)), ), ), ), ), ), ); expect( find.byType(LinearProgressIndicator), paints ..rrect(rrect: RRect.fromLTRBR(0.0, 0.0, 100.0, 4.0, const Radius.circular(10.0))) ..rrect( rrect: RRect.fromRectAndRadius( const Rect.fromLTRB(0.0, 0.0, 25.0, 4.0), const Radius.circular(10.0), ), ), ); expect(tester.binding.transientCallbackCount, 0); }); testWidgets('LinearProgressIndicator reflects controller value', (WidgetTester tester) async { final controller = AnimationController(vsync: tester, value: 0.5); addTearDown(controller.dispose); await tester.pumpWidget( MaterialApp( home: Material( child: Center( child: SizedBox(width: 200, child: LinearProgressIndicator(controller: controller)), ), ), ), ); expect( find.byType(LinearProgressIndicator), paints..rect(rect: const Rect.fromLTRB(127.79541015625, 0.0, 200.0, 4.0)), ); }); testWidgets('CircularProgressIndicator paint colors', (WidgetTester tester) async { const green = Color(0xFF00FF00); const blue = Color(0xFF0000FF); const red = Color(0xFFFF0000); // With valueColor & color provided await tester.pumpWidget( Theme( data: theme, child: const CircularProgressIndicator( color: red, valueColor: AlwaysStoppedAnimation(blue), ), ), ); expect(find.byType(CircularProgressIndicator), paintsExactlyCountTimes(#drawArc, 1)); expect(find.byType(CircularProgressIndicator), paints..arc(color: blue)); // With just color provided await tester.pumpWidget( Theme( data: theme, child: 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: theme.copyWith(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( Theme( data: theme, child: const CircularProgressIndicator(backgroundColor: green, color: blue), ), ); expect(find.byType(CircularProgressIndicator), paintsExactlyCountTimes(#drawArc, 2)); expect( find.byType(CircularProgressIndicator), paints ..arc(color: green) ..arc(color: blue), ); // With ProgressIndicatorTheme await tester.pumpWidget( Theme( data: theme.copyWith( progressIndicatorTheme: const ProgressIndicatorThemeData( color: green, circularTrackColor: blue, ), ), child: const CircularProgressIndicator(), ), ); expect(find.byType(CircularProgressIndicator), paintsExactlyCountTimes(#drawArc, 2)); expect( find.byType(CircularProgressIndicator), paints ..arc(color: blue) ..arc(color: green), ); }); testWidgets('RefreshProgressIndicator paint colors', (WidgetTester tester) async { const green = Color(0xFF00FF00); const blue = Color(0xFF0000FF); const red = Color(0xFFFF0000); // With valueColor & color provided await tester.pumpWidget( Theme( data: theme, child: const RefreshProgressIndicator( color: red, valueColor: AlwaysStoppedAnimation(blue), ), ), ); expect(find.byType(RefreshProgressIndicator), paintsExactlyCountTimes(#drawArc, 1)); expect(find.byType(RefreshProgressIndicator), paints..arc(color: blue)); // With just color provided await tester.pumpWidget( Theme( data: theme, child: const RefreshProgressIndicator(color: red), ), ); expect(find.byType(RefreshProgressIndicator), paintsExactlyCountTimes(#drawArc, 1)); expect(find.byType(RefreshProgressIndicator), paints..arc(color: red)); // With no color provided await tester.pumpWidget( Theme( data: theme.copyWith(colorScheme: ColorScheme.fromSwatch().copyWith(primary: green)), child: const RefreshProgressIndicator(), ), ); expect(find.byType(RefreshProgressIndicator), paintsExactlyCountTimes(#drawArc, 1)); expect(find.byType(RefreshProgressIndicator), paints..arc(color: green)); // With background await tester.pumpWidget( Theme( data: theme, child: const RefreshProgressIndicator(color: blue, backgroundColor: green), ), ); expect(find.byType(RefreshProgressIndicator), paintsExactlyCountTimes(#drawArc, 1)); expect(find.byType(RefreshProgressIndicator), paints..arc(color: blue)); final Material backgroundMaterial = tester.widget( find.descendant(of: find.byType(RefreshProgressIndicator), matching: find.byType(Material)), ); expect(backgroundMaterial.type, MaterialType.circle); expect(backgroundMaterial.color, green); // With ProgressIndicatorTheme await tester.pumpWidget( Theme( data: theme.copyWith( progressIndicatorTheme: const ProgressIndicatorThemeData( color: green, refreshBackgroundColor: blue, ), ), child: const RefreshProgressIndicator(), ), ); expect(find.byType(RefreshProgressIndicator), paintsExactlyCountTimes(#drawArc, 1)); expect(find.byType(RefreshProgressIndicator), paints..arc(color: green)); final Material themeBackgroundMaterial = tester.widget( find.descendant(of: find.byType(RefreshProgressIndicator), matching: find.byType(Material)), ); expect(themeBackgroundMaterial.type, MaterialType.circle); expect(themeBackgroundMaterial.color, blue); }); testWidgets('RefreshProgressIndicator with a round indicator', (WidgetTester tester) async { await tester.pumpWidget(const RefreshProgressIndicator()); expect( find.byType(RefreshProgressIndicator), paints..arc(strokeCap: StrokeCap.square), reason: 'Default indeterminate strokeCap is StrokeCap.square', ); await tester.pumpWidget( Theme( data: theme, child: const Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: RefreshProgressIndicator(strokeCap: StrokeCap.round), ), ), ), ), ); expect(find.byType(RefreshProgressIndicator), paints..arc(strokeCap: StrokeCap.round)); }); 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( Theme( data: theme, child: 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('Material2 - RefreshProgressIndicator uses expected animation', ( WidgetTester tester, ) async { final animationSheet = AnimationSheetBuilder(frameSize: const Size(50, 50)); addTearDown(animationSheet.dispose); await tester.pumpFrames( animationSheet.record( Theme(data: ThemeData(useMaterial3: false), child: const _RefreshProgressIndicatorGolden()), ), const Duration(seconds: 3), ); await expectLater( animationSheet.collate(20), matchesGoldenFile('m2_material.refresh_progress_indicator.png'), ); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 testWidgets('Material3 - RefreshProgressIndicator uses expected animation', ( WidgetTester tester, ) async { final animationSheet = AnimationSheetBuilder(frameSize: const Size(50, 50)); addTearDown(animationSheet.dispose); await tester.pumpFrames( animationSheet.record( Theme(data: ThemeData(), child: const _RefreshProgressIndicatorGolden()), ), const Duration(seconds: 3), ); await expectLater( animationSheet.collate(20), matchesGoldenFile('m3_material.refresh_progress_indicator.png'), ); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 testWidgets('Determinate CircularProgressIndicator stops the animator', ( WidgetTester tester, ) async { double? progressValue; late StateSetter setState; await tester.pumpWidget( Theme( data: theme, child: 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( Theme( data: theme, child: 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( Theme( data: theme, child: 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( Theme( data: theme, child: 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 label = 'Label'; const value = '25'; await tester.pumpWidget( Theme( data: theme, child: 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 label = 'Label'; await tester.pumpWidget( Theme( data: theme, child: 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( Theme( data: theme, child: 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 label = 'Progress'; await tester.pumpWidget( Theme( data: theme, child: 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 label = 'Label'; const value = '25'; await tester.pumpWidget( Theme( data: theme, child: 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 label = 'Label'; const value = '25'; await tester.pumpWidget( Theme( data: theme, child: 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('Material2 - Indeterminate CircularProgressIndicator uses expected animation', ( WidgetTester tester, ) async { final animationSheet = AnimationSheetBuilder(frameSize: const Size(40, 40)); addTearDown(animationSheet.dispose); await tester.pumpFrames( animationSheet.record( Theme( data: ThemeData(useMaterial3: false), child: const Directionality( textDirection: TextDirection.ltr, child: Padding(padding: EdgeInsets.all(4), child: CircularProgressIndicator()), ), ), ), const Duration(seconds: 2), ); await expectLater( animationSheet.collate(20), matchesGoldenFile('m2_material.circular_progress_indicator.indeterminate.png'), ); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 testWidgets('Material3 - Indeterminate CircularProgressIndicator uses expected animation', ( WidgetTester tester, ) async { final animationSheet = AnimationSheetBuilder(frameSize: const Size(40, 40)); addTearDown(animationSheet.dispose); await tester.pumpFrames( animationSheet.record( Theme( data: ThemeData(), child: const Directionality( textDirection: TextDirection.ltr, child: Padding(padding: EdgeInsets.all(4), child: CircularProgressIndicator()), ), ), ), const Duration(seconds: 2), ); await expectLater( animationSheet.collate(20), matchesGoldenFile('m3_material.circular_progress_indicator.indeterminate.png'), ); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 testWidgets( 'Adaptive CircularProgressIndicator displays CupertinoActivityIndicator in iOS', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( theme: ThemeData(), home: const Scaffold(body: Material(child: CircularProgressIndicator.adaptive())), ), ); expect(find.byType(CupertinoActivityIndicator), findsOneWidget); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS, }), ); testWidgets( 'Adaptive CircularProgressIndicator displays CupertinoActivityIndicator in iOS/macOS', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( theme: ThemeData(), home: const Scaffold( body: Material(child: CircularProgressIndicator.adaptive(value: 0.5)), ), ), ); expect(find.byType(CupertinoActivityIndicator), findsOneWidget); final double actualProgress = tester .widget(find.byType(CupertinoActivityIndicator)) .progress; expect(actualProgress, 0.5); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS, }), ); testWidgets( 'Adaptive CircularProgressIndicator can use backgroundColor to change tick color for iOS', (WidgetTester tester) async { const color = Color(0xFF5D3FD3); await tester.pumpWidget( MaterialApp( theme: ThemeData(), home: const Scaffold( body: Material(child: CircularProgressIndicator.adaptive(backgroundColor: color)), ), ), ); expect( find.byType(CupertinoActivityIndicator), paints..rrect( rrect: const RRect.fromLTRBXY(-1, -10 / 3, 1, -10, 1, 1), // The value of 47 comes from the alpha that is applied to the first // tick. color: color.withAlpha(47), ), ); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS, }), ); testWidgets( 'Adaptive CircularProgressIndicator does not display CupertinoActivityIndicator in non-iOS', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( theme: theme, home: const Scaffold(body: Material(child: CircularProgressIndicator.adaptive())), ), ); expect(find.byType(CupertinoActivityIndicator), findsNothing); }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.windows, TargetPlatform.linux, }), ); testWidgets('ProgressIndicatorTheme.wrap() always creates a new ProgressIndicatorTheme', ( WidgetTester tester, ) async { late BuildContext builderContext; const themeData = ProgressIndicatorThemeData( color: Color(0xFFFF0000), linearTrackColor: Color(0xFF00FF00), ); final progressTheme = ProgressIndicatorTheme( data: themeData, child: Builder( builder: (BuildContext context) { builderContext = context; return const LinearProgressIndicator(value: 0.5); }, ), ); await tester.pumpWidget( MaterialApp( home: Theme(data: theme, child: progressTheme), ), ); final Widget wrappedTheme = progressTheme.wrap(builderContext, Container()); // Make sure the returned widget is a new ProgressIndicatorTheme instance // with the same theme data as the original. expect(wrappedTheme, isNot(equals(progressTheme))); expect(wrappedTheme, isInstanceOf()); expect((wrappedTheme as ProgressIndicatorTheme).data, themeData); }); testWidgets('Material3 - Default size of CircularProgressIndicator', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Scaffold(body: Material(child: CircularProgressIndicator())), ), ); expect(tester.getSize(find.byType(CircularProgressIndicator)), const Size(36, 36)); }); testWidgets('Material3 - Default size of CircularProgressIndicator when year2023 is false', ( WidgetTester tester, ) async { await tester.pumpWidget( const MaterialApp( home: Scaffold(body: Material(child: CircularProgressIndicator(year2023: false))), ), ); expect(tester.getSize(find.byType(CircularProgressIndicator)), const Size(48, 48)); }); testWidgets('RefreshProgressIndicator using fields correctly', (WidgetTester tester) async { Future pumpIndicator(RefreshProgressIndicator indicator) { return tester.pumpWidget(Theme(data: theme, child: indicator)); } // With default values. await pumpIndicator(const RefreshProgressIndicator()); Material material = tester.widget( find.descendant(of: find.byType(RefreshProgressIndicator), matching: find.byType(Material)), ); Padding padding = tester.widget( find .descendant(of: find.byType(RefreshProgressIndicator), matching: find.byType(Padding)) .first, ); Padding innerPadding = tester.widget( find .descendant( of: find.descendant( of: find.byType(RefreshProgressIndicator), matching: find.byType(Material), ), matching: find.byType(Padding), ) .last, ); expect(material.elevation, 2.0); expect(padding.padding, const EdgeInsets.all(4.0)); expect(innerPadding.padding, const EdgeInsets.all(12.0)); // With values provided. const testElevation = 1.0; const EdgeInsetsGeometry testIndicatorMargin = EdgeInsets.all(6.0); const EdgeInsetsGeometry testIndicatorPadding = EdgeInsets.all(10.0); await pumpIndicator( const RefreshProgressIndicator( elevation: testElevation, indicatorMargin: testIndicatorMargin, indicatorPadding: testIndicatorPadding, ), ); material = tester.widget( find.descendant(of: find.byType(RefreshProgressIndicator), matching: find.byType(Material)), ); padding = tester.widget( find .descendant(of: find.byType(RefreshProgressIndicator), matching: find.byType(Padding)) .first, ); innerPadding = tester.widget( find .descendant( of: find.descendant( of: find.byType(RefreshProgressIndicator), matching: find.byType(Material), ), matching: find.byType(Padding), ) .last, ); expect(material.elevation, testElevation); expect(padding.padding, testIndicatorMargin); expect(innerPadding.padding, testIndicatorPadding); }); testWidgets('LinearProgressIndicator default stop indicator when year2023 is false', ( WidgetTester tester, ) async { Widget buildIndicator({required TextDirection textDirection}) { return Directionality( textDirection: textDirection, child: const Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(year2023: false, value: 0.5), ), ), ); } await tester.pumpWidget(buildIndicator(textDirection: TextDirection.ltr)); expect( find.byType(LinearProgressIndicator), paints..circle(x: 198.0, y: 2.0, radius: 2.0, color: theme.colorScheme.primary), ); await tester.pumpWidget(buildIndicator(textDirection: TextDirection.rtl)); expect( find.byType(LinearProgressIndicator), paints..circle(x: 2.0, y: 2.0, radius: 2.0, color: theme.colorScheme.primary), ); }); testWidgets( 'Determinate LinearProgressIndicator when year2023 is false', (WidgetTester tester) async { Future getGoldenImage({ required TextDirection textDirection, double? trackGap, }) async { final value = ValueNotifier(0); addTearDown(value.dispose); final animationSheet = AnimationSheetBuilder(frameSize: const Size(250, 30)); addTearDown(animationSheet.dispose); final Widget target = Material( child: Directionality( textDirection: textDirection, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 50), child: Center( child: ValueListenableBuilder( valueListenable: value, builder: (BuildContext context, double value, _) { return LinearProgressIndicator( year2023: false, value: value, trackGap: trackGap, ); }, ), ), ), ), ); for (var i = 0; i <= 50; i++) { value.value = i * 0.02; await tester.pumpWidget(animationSheet.record(target)); } return animationSheet.collate(3); } await expectLater( await getGoldenImage(textDirection: TextDirection.ltr), matchesGoldenFile('m3_linear_progress_indicator.determinate.ltr.png'), ); await expectLater( await getGoldenImage(textDirection: TextDirection.rtl), matchesGoldenFile('m3_linear_progress_indicator.determinate.rtl.png'), ); await expectLater( await getGoldenImage(textDirection: TextDirection.ltr, trackGap: 20), matchesGoldenFile('m3_linear_progress_indicator.determinate.ltr.custom_track_gap.png'), ); await expectLater( await getGoldenImage(textDirection: TextDirection.rtl, trackGap: 20), matchesGoldenFile('m3_linear_progress_indicator.determinate.rtl.custom_track_gap.png'), ); }, skip: isBrowser, // [intended] https://github.com/flutter/flutter/issues/56001 ); testWidgets( 'Indeterminate LinearProgressIndicator when year2023 is false', (WidgetTester tester) async { Future getGoldenImage({ required TextDirection textDirection, double? trackGap, }) async { final animationSheet = AnimationSheetBuilder(frameSize: const Size(250, 30)); addTearDown(animationSheet.dispose); final Widget target = Material( child: Directionality( textDirection: textDirection, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 50), child: Center(child: LinearProgressIndicator(year2023: false, trackGap: trackGap)), ), ), ); await tester.pumpFrames(animationSheet.record(target), const Duration(milliseconds: 1800)); return animationSheet.collate(3); } await expectLater( await getGoldenImage(textDirection: TextDirection.ltr), matchesGoldenFile('m3_linear_progress_indicator.indeterminate.ltr.png'), ); await expectLater( await getGoldenImage(textDirection: TextDirection.rtl), matchesGoldenFile('m3_linear_progress_indicator.indeterminate.rtl.png'), ); await expectLater( await getGoldenImage(textDirection: TextDirection.ltr, trackGap: 20), matchesGoldenFile('m3_linear_progress_indicator.indeterminate.ltr.custom_track_gap.png'), ); await expectLater( await getGoldenImage(textDirection: TextDirection.rtl, trackGap: 20), matchesGoldenFile('m3_linear_progress_indicator.indeterminate.rtl.custom_track_gap.png'), ); }, skip: isBrowser, // [intended] https://github.com/flutter/flutter/issues/56001 ); testWidgets('Indeterminate LinearProgressIndicator does not paint stop indicator', ( WidgetTester tester, ) async { Widget buildIndicator({double? value}) { return Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(year2023: false, value: value), ), ), ); } // Determinate LinearProgressIndicator paints stop indicator. await tester.pumpWidget(buildIndicator(value: 0.5)); expect( find.byType(LinearProgressIndicator), // Stop indicator. paints..circle(x: 198.0, y: 2.0, radius: 2.0, color: theme.colorScheme.primary), ); // Indeterminate LinearProgressIndicator does not paint stop indicator. await tester.pumpWidget(buildIndicator()); expect( find.byType(LinearProgressIndicator), // Stop indicator. isNot(paints..circle(x: 198.0, y: 2.0, radius: 2.0, color: theme.colorScheme.primary)), ); }); testWidgets('Can customise LinearProgressIndicator stop indicator when year2023 is false', ( WidgetTester tester, ) async { const stopIndicatorColor = Color(0XFF00FF00); const stopIndicatorRadius = 5.0; Widget buildIndicator({Color? stopIndicatorColor, double? stopIndicatorRadius}) { return Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator( year2023: false, stopIndicatorColor: stopIndicatorColor, stopIndicatorRadius: stopIndicatorRadius, minHeight: 20.0, value: 0.5, ), ), ), ); } // Test customized stop indicator. await tester.pumpWidget( buildIndicator( stopIndicatorColor: stopIndicatorColor, stopIndicatorRadius: stopIndicatorRadius, ), ); expect( find.byType(LinearProgressIndicator), // Stop indicator. paints..circle(x: 190.0, y: 10.0, radius: stopIndicatorRadius, color: stopIndicatorColor), ); // Remove stop indicator. await tester.pumpWidget(buildIndicator(stopIndicatorRadius: 0)); expect( find.byType(LinearProgressIndicator), // Stop indicator. isNot(paints..circle(color: stopIndicatorColor)), ); // Test stop indicator with transparent color. await tester.pumpWidget(buildIndicator(stopIndicatorColor: const Color(0x00000000))); expect( find.byType(LinearProgressIndicator), // Stop indicator. paints..circle(color: const Color(0x00000000)), ); }); testWidgets('Stop indicator size cannot be larger than the progress indicator', ( WidgetTester tester, ) async { Widget buildIndicator({double? stopIndicatorRadius, double? minHeight}) { return Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator( year2023: false, stopIndicatorRadius: stopIndicatorRadius, minHeight: minHeight, value: 0.5, ), ), ), ); } // Test stop indicator radius equals to minHeight. await tester.pumpWidget(buildIndicator(stopIndicatorRadius: 10.0, minHeight: 20.0)); expect( find.byType(LinearProgressIndicator), paints..circle(x: 190.0, y: 10.0, radius: 10.0, color: theme.colorScheme.primary), ); // Test stop indicator radius larger than minHeight. await tester.pumpWidget(buildIndicator(stopIndicatorRadius: 30.0, minHeight: 20.0)); expect( find.byType(LinearProgressIndicator), // Stop indicator radius is clamped to minHeight. paints..circle(x: 190.0, y: 10.0, radius: 10.0, color: theme.colorScheme.primary), ); }); testWidgets('LinearProgressIndicator default track gap when year2023 is false', ( WidgetTester tester, ) async { const defaultTrackGap = 4.0; Widget buildIndicator({required TextDirection textDirection}) { return Directionality( textDirection: textDirection, child: const Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(year2023: false, value: 0.5), ), ), ); } // Test default track gap in LTR. await tester.pumpWidget(buildIndicator(textDirection: TextDirection.ltr)); expect( find.byType(LinearProgressIndicator), paints // Track. ..rrect( rrect: RRect.fromLTRBR( 100.0 + defaultTrackGap, 0.0, 200.0, 4.0, const Radius.circular(2.0), ), color: theme.colorScheme.secondaryContainer, ) // Active track. ..rrect( rrect: RRect.fromLTRBR(0.0, 0.0, 100.0, 4.0, const Radius.circular(2.0)), color: theme.colorScheme.primary, ), ); // Test default track gap in RTL. await tester.pumpWidget(buildIndicator(textDirection: TextDirection.rtl)); expect( find.byType(LinearProgressIndicator), paints // Track. ..rrect( rrect: RRect.fromLTRBR( 0.0, 0.0, 100.0 - defaultTrackGap, 4.0, const Radius.circular(2.0), ), color: theme.colorScheme.secondaryContainer, ) // Active track. ..rrect( rrect: RRect.fromLTRBR(100.0, 0.0, 200.0, 4.0, const Radius.circular(2.0)), color: theme.colorScheme.primary, ), ); }); testWidgets('Can customise LinearProgressIndicator track gap when year2023 is false', ( WidgetTester tester, ) async { const customTrackGap = 12.0; const noTrackGap = 0.0; Widget buildIndicator({double? trackGap}) { return Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox( width: 200.0, child: LinearProgressIndicator(year2023: false, trackGap: trackGap, value: 0.5), ), ), ); } // Test customized track gap. await tester.pumpWidget(buildIndicator(trackGap: customTrackGap)); expect( find.byType(LinearProgressIndicator), paints // Track. ..rrect( rrect: RRect.fromLTRBR( 100.0 + customTrackGap, 0.0, 200.0, 4.0, const Radius.circular(2.0), ), color: theme.colorScheme.secondaryContainer, ) // Stop indicator. ..circle() // Active track. ..rrect( rrect: RRect.fromLTRBR(0.0, 0.0, 100.0, 4.0, const Radius.circular(2.0)), color: theme.colorScheme.primary, ), ); // Remove track gap. await tester.pumpWidget(buildIndicator(trackGap: noTrackGap)); expect( find.byType(LinearProgressIndicator), paints // Track. ..rrect( rrect: RRect.fromLTRBR(0.0, 0.0, 200.0, 4.0, const Radius.circular(2.0)), color: theme.colorScheme.secondaryContainer, ) // Stop indicator. ..circle() // Active indicator. ..rrect( rrect: RRect.fromLTRBR(0.0, 0.0, 100.0, 4.0, const Radius.circular(2.0)), color: theme.colorScheme.primary, ), ); }); testWidgets('Default determinate CircularProgressIndicator when year2023 is false', ( WidgetTester tester, ) async { const EdgeInsetsGeometry padding = EdgeInsets.all(4.0); await tester.pumpWidget( MaterialApp( theme: theme, home: const Center(child: CircularProgressIndicator(year2023: false, value: 0.5)), ), ); final Size indicatorBoxSize = tester.getSize( find.descendant( of: find.byType(CircularProgressIndicator), matching: find.byType(ConstrainedBox), ), ); expect( tester.getSize(find.byType(CircularProgressIndicator)), equals( Size( indicatorBoxSize.width + padding.horizontal, indicatorBoxSize.height + padding.vertical, ), ), ); expect( find.byType(CircularProgressIndicator), paints // Track. ..arc( rect: const Rect.fromLTRB(2.0, 2.0, 38.0, 38.0), color: theme.colorScheme.secondaryContainer, strokeWidth: 4.0, strokeCap: StrokeCap.round, style: PaintingStyle.stroke, ) // Active indicator. ..arc( rect: const Rect.fromLTRB(2.0, 2.0, 38.0, 38.0), color: theme.colorScheme.primary, strokeWidth: 4.0, strokeCap: StrokeCap.round, style: PaintingStyle.stroke, ), ); await expectLater( find.byType(CircularProgressIndicator), matchesGoldenFile('circular_progress_indicator_determinate_year2023_false.png'), ); }); testWidgets('Default indeterminate CircularProgressIndicator when year2023 is false', ( WidgetTester tester, ) async { const EdgeInsetsGeometry padding = EdgeInsets.all(4.0); await tester.pumpWidget( MaterialApp( theme: theme, home: const Center(child: CircularProgressIndicator(year2023: false)), ), ); // Advance the animation. await tester.pump(const Duration(milliseconds: 200)); final Size indicatorBoxSize = tester.getSize( find.descendant( of: find.byType(CircularProgressIndicator), matching: find.byType(ConstrainedBox), ), ); expect( tester.getSize(find.byType(CircularProgressIndicator)), equals( Size( indicatorBoxSize.width + padding.horizontal, indicatorBoxSize.height + padding.vertical, ), ), ); expect( find.byType(CircularProgressIndicator), paints // Active indicator. ..arc( rect: const Rect.fromLTRB(2.0, 2.0, 38.0, 38.0), color: theme.colorScheme.primary, strokeWidth: 4.0, strokeCap: StrokeCap.round, style: PaintingStyle.stroke, ), ); await expectLater( find.byType(CircularProgressIndicator), matchesGoldenFile('circular_progress_indicator_indeterminate_year2023_false.png'), ); }); testWidgets('CircularProgressIndicator track gap can be adjusted when year2023 is false', ( WidgetTester tester, ) async { Widget buildIndicator({double? trackGap}) { return MaterialApp( home: Center( child: CircularProgressIndicator(year2023: false, trackGap: trackGap, value: 0.5), ), ); } await tester.pumpWidget(buildIndicator()); await expectLater( find.byType(CircularProgressIndicator), matchesGoldenFile('circular_progress_indicator_default_track_gap_year2023_false.png'), ); await tester.pumpWidget(buildIndicator(trackGap: 12.0)); await expectLater( find.byType(CircularProgressIndicator), matchesGoldenFile('circular_progress_indicator_custom_track_gap_year2023_false.png'), ); await tester.pumpWidget(buildIndicator(trackGap: 0.0)); await expectLater( find.byType(CircularProgressIndicator), matchesGoldenFile('circular_progress_indicator_no_track_gap_year2023_false.png'), ); }); testWidgets('Can override CircularProgressIndicator stroke cap when year2023 is false', ( WidgetTester tester, ) async { const StrokeCap strokeCap = StrokeCap.square; await tester.pumpWidget( const MaterialApp( home: Center( child: CircularProgressIndicator(year2023: false, strokeCap: strokeCap, value: 0.5), ), ), ); expect( find.byType(CircularProgressIndicator), paints // Track. ..arc(strokeCap: strokeCap) // Active indicator. ..arc(strokeCap: strokeCap), ); await expectLater( find.byType(CircularProgressIndicator), matchesGoldenFile('circular_progress_indicator_custom_stroke_cap_year2023_false.png'), ); }); testWidgets('CircularProgressIndicator.constraints can override default size', ( WidgetTester tester, ) async { const size = Size(64, 64); await tester.pumpWidget( MaterialApp( home: Center( child: CircularProgressIndicator( constraints: BoxConstraints(minWidth: size.width, minHeight: size.height), value: 0.5, ), ), ), ); expect(tester.getSize(find.byType(CircularProgressIndicator)), equals(size)); }); testWidgets('CircularProgressIndicator padding can be customized', (WidgetTester tester) async { const EdgeInsetsGeometry padding = EdgeInsets.all(12.0); await tester.pumpWidget( const MaterialApp( home: Center( child: CircularProgressIndicator(padding: padding, year2023: false, value: 0.5), ), ), ); final Size indicatorBoxSize = tester.getSize( find.descendant( of: find.byType(CircularProgressIndicator), matching: find.byType(ConstrainedBox), ), ); expect( tester.getSize(find.byType(CircularProgressIndicator)), equals( Size( indicatorBoxSize.width + padding.horizontal, indicatorBoxSize.height + padding.vertical, ), ), ); }); testWidgets('CircularProgressIndicator reflects controller value', (WidgetTester tester) async { final controller = AnimationController(vsync: tester, value: 0.5); addTearDown(controller.dispose); await tester.pumpWidget( MaterialApp( home: Material( child: Center( child: SizedBox( width: 200, height: 200, child: AnimatedBuilder( animation: controller, builder: (BuildContext context, Widget? child) { return CircularProgressIndicator(controller: controller); }, ), ), ), ), ), ); expect( find.byType(CircularProgressIndicator), paints..arc(startAngle: 1.5707963267948966, sweepAngle: 0.001), ); }); testWidgets('RefreshProgressIndicator does not crash at zero area', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Center(child: SizedBox.shrink(child: RefreshProgressIndicator())), ), ); expect(tester.getSize(find.byType(RefreshProgressIndicator)), Size.zero); }); testWidgets('CircularProgressIndicator does not crash at zero area', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Center(child: SizedBox.shrink(child: CircularProgressIndicator())), ), ); expect(tester.getSize(find.byType(CircularProgressIndicator)), Size.zero); }); testWidgets('LinearProgressIndicator does not crash at zero area', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Center(child: SizedBox.shrink(child: LinearProgressIndicator())), ), ); expect(tester.getSize(find.byType(LinearProgressIndicator)), Size.zero); }); testWidgets('LinearProgressIndicator clamps value', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); // Test value > 1.0 await tester.pumpWidget( const MaterialApp(home: Scaffold(body: LinearProgressIndicator(value: 1.5))), ); expect( tester.getSemantics(find.byType(LinearProgressIndicator)), matchesSemantics(value: '100', textDirection: TextDirection.ltr), ); // Test value < 0.0 await tester.pumpWidget( const MaterialApp(home: Scaffold(body: LinearProgressIndicator(value: -0.5))), ); expect( tester.getSemantics(find.byType(LinearProgressIndicator)), matchesSemantics(value: '0', textDirection: TextDirection.ltr), ); handle.dispose(); }); testWidgets('CircularProgressIndicator clamps value', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); // Test value > 1.0 await tester.pumpWidget( const MaterialApp(home: Scaffold(body: CircularProgressIndicator(value: 1.5))), ); expect( tester.getSemantics(find.byType(CircularProgressIndicator)), matchesSemantics(value: '100', textDirection: TextDirection.ltr), ); // Test value < 0.0 await tester.pumpWidget( const MaterialApp(home: Scaffold(body: CircularProgressIndicator(value: -0.5))), ); expect( tester.getSemantics(find.byType(CircularProgressIndicator)), matchesSemantics(value: '0', textDirection: TextDirection.ltr), ); handle.dispose(); }); testWidgets('RefreshProgressIndicator clamps value', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); // Test value > 1.0 await tester.pumpWidget( const MaterialApp(home: Scaffold(body: RefreshProgressIndicator(value: 1.5))), ); expect( tester.getSemantics(find.byType(RefreshProgressIndicator)), matchesSemantics(value: '100', textDirection: TextDirection.ltr), ); // Test value < 0.0 await tester.pumpWidget( const MaterialApp(home: Scaffold(body: RefreshProgressIndicator(value: -0.5))), ); expect( tester.getSemantics(find.byType(RefreshProgressIndicator)), matchesSemantics(value: '0', textDirection: TextDirection.ltr), ); handle.dispose(); }); } class _RefreshProgressIndicatorGolden extends StatefulWidget { const _RefreshProgressIndicatorGolden(); @override _RefreshProgressIndicatorGoldenState createState() => _RefreshProgressIndicatorGoldenState(); } class _RefreshProgressIndicatorGoldenState extends State<_RefreshProgressIndicatorGolden> with SingleTickerProviderStateMixin { late final AnimationController controller = AnimationController(vsync: this, duration: const Duration(seconds: 1)) ..forward() ..addListener(() { setState(() {}); }) ..addStatusListener((AnimationStatus status) { if (status.isCompleted) { indeterminate = true; } }); bool indeterminate = false; @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Center( child: Directionality( textDirection: TextDirection.ltr, child: RefreshProgressIndicator(value: indeterminate ? null : controller.value), ), ); } }