flutter_flutter/packages/flutter/test/material/progress_indicator_test.dart
chunhtai 423a30323c
Relands "Feat: Add a11y for loading indicators (#165173)" (#178402)
This reverts commit ef29db350f0951ab976e2fdb5d092e65578329e5.

<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

reland https://github.com/flutter/flutter/pull/165173

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2026-01-02 22:43:24 +00:00

2113 lines
65 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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(<String>['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<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(
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<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(
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 <Widget>[LinearProgressIndicator(value: 0.0)]),
),
),
);
final List<Layer> layers1 = tester.layers;
await tester.pumpWidget(
Theme(
data: theme,
child: 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 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 themes 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 themes 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<Color>(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<Color>(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>{
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<CupertinoActivityIndicator>(find.byType(CupertinoActivityIndicator))
.progress;
expect(actualProgress, 0.5);
},
variant: const TargetPlatformVariant(<TargetPlatform>{
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>{
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>{
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<ProgressIndicatorTheme>());
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<void> 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<ui.Image> getGoldenImage({
required TextDirection textDirection,
double? trackGap,
}) async {
final value = ValueNotifier<double>(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<double>(
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<ui.Image> 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),
),
);
}
}