Unverified Commit d76e3abf authored by Kostia Sokolovskyi's avatar Kostia Sokolovskyi Committed by GitHub

Fix memory leaks in DateRangePickerDialog. (#136034)

This PR mainly fixes several memory leaks in the `DateRangePickerDialog`.

### Description
- Fixes https://github.com/flutter/flutter/issues/136033 by:
    1) adding a disposal of several `RestorableValue`;
    2) creating a separate `_DayItem` stateful widget that creates/updates/disposes internal `MaterialStatesController`.
- Marks https://github.com/flutter/flutter/issues/136036.

### Tests
- Updates `test/material/date_picker_theme_test.dart` to use `testWidgetsWithLeakTracking`;
- Updates `test/material/date_range_picker_test.dart` to use `testWidgetsWithLeakTracking`.
parent 670e6ba1
...@@ -1353,6 +1353,15 @@ class _DateRangePickerDialogState extends State<DateRangePickerDialog> with Rest ...@@ -1353,6 +1353,15 @@ class _DateRangePickerDialogState extends State<DateRangePickerDialog> with Rest
registerForRestoration(_autoValidate, 'autovalidate'); registerForRestoration(_autoValidate, 'autovalidate');
} }
@override
void dispose() {
_entryMode.dispose();
_selectedStart.dispose();
_selectedEnd.dispose();
_autoValidate.dispose();
super.dispose();
}
void _handleOk() { void _handleOk() {
if (_entryMode.value == DatePickerEntryMode.input || _entryMode.value == DatePickerEntryMode.inputOnly) { if (_entryMode.value == DatePickerEntryMode.input || _entryMode.value == DatePickerEntryMode.inputOnly) {
final _InputDateRangePickerState picker = _inputPickerKey.currentState!; final _InputDateRangePickerState picker = _inputPickerKey.currentState!;
...@@ -2368,143 +2377,32 @@ class _MonthItemState extends State<_MonthItem> { ...@@ -2368,143 +2377,32 @@ class _MonthItemState extends State<_MonthItem> {
} }
Widget _buildDayItem(BuildContext context, DateTime dayToBuild, int firstDayOffset, int daysInMonth) { Widget _buildDayItem(BuildContext context, DateTime dayToBuild, int firstDayOffset, int daysInMonth) {
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
final TextTheme textTheme = theme.textTheme;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
final TextDirection textDirection = Directionality.of(context);
final Color highlightColor = _highlightColor(context);
final int day = dayToBuild.day; final int day = dayToBuild.day;
final bool isDisabled = dayToBuild.isAfter(widget.lastDate) || dayToBuild.isBefore(widget.firstDate); final bool isDisabled = dayToBuild.isAfter(widget.lastDate) || dayToBuild.isBefore(widget.firstDate);
BoxDecoration? decoration;
TextStyle? itemStyle = textTheme.bodyMedium;
final bool isRangeSelected = widget.selectedDateStart != null && widget.selectedDateEnd != null; final bool isRangeSelected = widget.selectedDateStart != null && widget.selectedDateEnd != null;
final bool isSelectedDayStart = widget.selectedDateStart != null && dayToBuild.isAtSameMomentAs(widget.selectedDateStart!); final bool isSelectedDayStart = widget.selectedDateStart != null && dayToBuild.isAtSameMomentAs(widget.selectedDateStart!);
final bool isSelectedDayEnd = widget.selectedDateEnd != null && dayToBuild.isAtSameMomentAs(widget.selectedDateEnd!); final bool isSelectedDayEnd = widget.selectedDateEnd != null && dayToBuild.isAtSameMomentAs(widget.selectedDateEnd!);
final bool isInRange = isRangeSelected && final bool isInRange = isRangeSelected &&
dayToBuild.isAfter(widget.selectedDateStart!) && dayToBuild.isAfter(widget.selectedDateStart!) &&
dayToBuild.isBefore(widget.selectedDateEnd!); dayToBuild.isBefore(widget.selectedDateEnd!);
final bool isOneDayRange = isRangeSelected && widget.selectedDateStart == widget.selectedDateEnd;
T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) { final bool isToday = DateUtils.isSameDay(widget.currentDate, dayToBuild);
return getProperty(datePickerTheme) ?? getProperty(defaults);
} return _DayItem(
day: dayToBuild,
T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) { focusNode: _dayFocusNodes[day - 1],
return effectiveValue( onChanged: widget.onChanged,
(DatePickerThemeData? theme) { onFocusChange: _dayFocusChanged,
return getProperty(theme)?.resolve(states); highlightColor: _highlightColor(context),
}, isDisabled: isDisabled,
); isRangeSelected: isRangeSelected,
} isSelectedDayStart: isSelectedDayStart,
isSelectedDayEnd: isSelectedDayEnd,
final Set<MaterialState> states = <MaterialState>{ isInRange: isInRange,
if (isDisabled) MaterialState.disabled, isOneDayRange: isOneDayRange,
if (isSelectedDayStart || isSelectedDayEnd) MaterialState.selected, isToday: isToday,
};
final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => theme?.dayForegroundColor, states);
final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => theme?.dayBackgroundColor, states);
final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) => effectiveValue(
(DatePickerThemeData? theme) =>
isInRange
? theme?.rangeSelectionOverlayColor?.resolve(states)
: theme?.dayOverlayColor?.resolve(states),
)
);
_HighlightPainter? highlightPainter;
if (isSelectedDayStart || isSelectedDayEnd) {
// The selected start and end dates gets a circle background
// highlight, and a contrasting text color.
itemStyle = textTheme.bodyMedium?.apply(color: dayForegroundColor);
decoration = BoxDecoration(
color: dayBackgroundColor,
shape: BoxShape.circle,
);
if (isRangeSelected && widget.selectedDateStart != widget.selectedDateEnd) {
final _HighlightPainterStyle style = isSelectedDayStart
? _HighlightPainterStyle.highlightTrailing
: _HighlightPainterStyle.highlightLeading;
highlightPainter = _HighlightPainter(
color: highlightColor,
style: style,
textDirection: textDirection,
);
}
} else if (isInRange) {
// The days within the range get a light background highlight.
highlightPainter = _HighlightPainter(
color: highlightColor,
style: _HighlightPainterStyle.highlightAll,
textDirection: textDirection,
);
} else if (isDisabled) {
itemStyle = textTheme.bodyMedium?.apply(color: colorScheme.onSurface.withOpacity(0.38));
} else if (DateUtils.isSameDay(widget.currentDate, dayToBuild)) {
// The current day gets a different text color and a circle stroke
// border.
itemStyle = textTheme.bodyMedium?.apply(color: colorScheme.primary);
decoration = BoxDecoration(
border: Border.all(color: colorScheme.primary),
shape: BoxShape.circle,
);
}
// We want the day of month to be spoken first irrespective of the
// locale-specific preferences or TextDirection. This is because
// an accessibility user is more likely to be interested in the
// day of month before the rest of the date, as they are looking
// for the day of month. To do that we prepend day of month to the
// formatted full date.
final String semanticLabelSuffix = DateUtils.isSameDay(widget.currentDate, dayToBuild) ? ', ${localizations.currentDateLabel}' : '';
String semanticLabel = '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}$semanticLabelSuffix';
if (isSelectedDayStart) {
semanticLabel = localizations.dateRangeStartDateSemanticLabel(semanticLabel);
} else if (isSelectedDayEnd) {
semanticLabel = localizations.dateRangeEndDateSemanticLabel(semanticLabel);
}
Widget dayWidget = Container(
decoration: decoration,
child: Center(
child: Semantics(
label: semanticLabel,
selected: isSelectedDayStart || isSelectedDayEnd,
child: ExcludeSemantics(
child: Text(localizations.formatDecimal(day), style: itemStyle),
),
),
),
); );
if (highlightPainter != null) {
dayWidget = CustomPaint(
painter: highlightPainter,
child: dayWidget,
);
}
if (!isDisabled) {
dayWidget = InkResponse(
focusNode: _dayFocusNodes[day - 1],
onTap: () => widget.onChanged(dayToBuild),
radius: _monthItemRowHeight / 2 + 4,
statesController: MaterialStatesController(states),
overlayColor: dayOverlayColor,
onFocusChange: _dayFocusChanged,
child: dayWidget,
);
}
return dayWidget;
} }
Widget _buildEdgeContainer(BuildContext context, bool isHighlighted) { Widget _buildEdgeContainer(BuildContext context, bool isHighlighted) {
...@@ -2618,6 +2516,194 @@ class _MonthItemState extends State<_MonthItem> { ...@@ -2618,6 +2516,194 @@ class _MonthItemState extends State<_MonthItem> {
} }
} }
class _DayItem extends StatefulWidget {
const _DayItem({
required this.day,
required this.focusNode,
required this.onChanged,
required this.onFocusChange,
required this.highlightColor,
required this.isDisabled,
required this.isRangeSelected,
required this.isSelectedDayStart,
required this.isSelectedDayEnd,
required this.isInRange,
required this.isOneDayRange,
required this.isToday,
});
final DateTime day;
final FocusNode focusNode;
final ValueChanged<DateTime> onChanged;
final ValueChanged<bool> onFocusChange;
final Color highlightColor;
final bool isDisabled;
final bool isRangeSelected;
final bool isSelectedDayStart;
final bool isSelectedDayEnd;
final bool isInRange;
final bool isOneDayRange;
final bool isToday;
@override
State<_DayItem> createState() => _DayItemState();
}
class _DayItemState extends State<_DayItem> {
final MaterialStatesController _statesController = MaterialStatesController();
@override
void dispose() {
_statesController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
final TextTheme textTheme = theme.textTheme;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
final TextDirection textDirection = Directionality.of(context);
final Color highlightColor = widget.highlightColor;
BoxDecoration? decoration;
TextStyle? itemStyle = textTheme.bodyMedium;
T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
return getProperty(datePickerTheme) ?? getProperty(defaults);
}
T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
return effectiveValue(
(DatePickerThemeData? theme) {
return getProperty(theme)?.resolve(states);
},
);
}
final Set<MaterialState> states = <MaterialState>{
if (widget.isDisabled) MaterialState.disabled,
if (widget.isSelectedDayStart || widget.isSelectedDayEnd) MaterialState.selected,
};
_statesController.value = states;
final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => theme?.dayForegroundColor, states);
final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => theme?.dayBackgroundColor, states);
final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) => effectiveValue(
(DatePickerThemeData? theme) => widget.isInRange
? theme?.rangeSelectionOverlayColor?.resolve(states)
: theme?.dayOverlayColor?.resolve(states),
)
);
_HighlightPainter? highlightPainter;
if (widget.isSelectedDayStart || widget.isSelectedDayEnd) {
// The selected start and end dates gets a circle background
// highlight, and a contrasting text color.
itemStyle = textTheme.bodyMedium?.apply(color: dayForegroundColor);
decoration = BoxDecoration(
color: dayBackgroundColor,
shape: BoxShape.circle,
);
if (widget.isRangeSelected && !widget.isOneDayRange) {
final _HighlightPainterStyle style = widget.isSelectedDayStart
? _HighlightPainterStyle.highlightTrailing
: _HighlightPainterStyle.highlightLeading;
highlightPainter = _HighlightPainter(
color: highlightColor,
style: style,
textDirection: textDirection,
);
}
} else if (widget.isInRange) {
// The days within the range get a light background highlight.
highlightPainter = _HighlightPainter(
color: highlightColor,
style: _HighlightPainterStyle.highlightAll,
textDirection: textDirection,
);
} else if (widget.isDisabled) {
itemStyle = textTheme.bodyMedium?.apply(color: colorScheme.onSurface.withOpacity(0.38));
} else if (widget.isToday) {
// The current day gets a different text color and a circle stroke
// border.
itemStyle = textTheme.bodyMedium?.apply(color: colorScheme.primary);
decoration = BoxDecoration(
border: Border.all(color: colorScheme.primary),
shape: BoxShape.circle,
);
}
final String dayText = localizations.formatDecimal(widget.day.day);
// We want the day of month to be spoken first irrespective of the
// locale-specific preferences or TextDirection. This is because
// an accessibility user is more likely to be interested in the
// day of month before the rest of the date, as they are looking
// for the day of month. To do that we prepend day of month to the
// formatted full date.
final String semanticLabelSuffix = widget.isToday ? ', ${localizations.currentDateLabel}' : '';
String semanticLabel = '$dayText, ${localizations.formatFullDate(widget.day)}$semanticLabelSuffix';
if (widget.isSelectedDayStart) {
semanticLabel = localizations.dateRangeStartDateSemanticLabel(semanticLabel);
} else if (widget.isSelectedDayEnd) {
semanticLabel = localizations.dateRangeEndDateSemanticLabel(semanticLabel);
}
Widget dayWidget = Container(
decoration: decoration,
child: Center(
child: Semantics(
label: semanticLabel,
selected: widget.isSelectedDayStart || widget.isSelectedDayEnd,
child: ExcludeSemantics(
child: Text(dayText, style: itemStyle),
),
),
),
);
if (highlightPainter != null) {
dayWidget = CustomPaint(
painter: highlightPainter,
child: dayWidget,
);
}
if (!widget.isDisabled) {
dayWidget = InkResponse(
focusNode: widget.focusNode,
onTap: () => widget.onChanged(widget.day),
radius: _monthItemRowHeight / 2 + 4,
statesController: _statesController,
overlayColor: dayOverlayColor,
onFocusChange: widget.onFocusChange,
child: dayWidget,
);
}
return dayWidget;
}
}
/// Determines which style to use to paint the highlight. /// Determines which style to use to paint the highlight.
enum _HighlightPainterStyle { enum _HighlightPainterStyle {
/// Paints nothing. /// Paints nothing.
......
...@@ -7,6 +7,7 @@ import 'package:flutter/gestures.dart'; ...@@ -7,6 +7,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
void main() { void main() {
const DatePickerThemeData datePickerTheme = DatePickerThemeData( const DatePickerThemeData datePickerTheme = DatePickerThemeData(
...@@ -136,7 +137,7 @@ void main() { ...@@ -136,7 +137,7 @@ void main() {
expect(theme.confirmButtonStyle, null); expect(theme.confirmButtonStyle, null);
}); });
testWidgets('DatePickerTheme.defaults M3 defaults', (WidgetTester tester) async { testWidgetsWithLeakTracking('DatePickerTheme.defaults M3 defaults', (WidgetTester tester) async {
late final DatePickerThemeData m3; // M3 Defaults late final DatePickerThemeData m3; // M3 Defaults
late final ThemeData theme; late final ThemeData theme;
late final ColorScheme colorScheme; late final ColorScheme colorScheme;
...@@ -213,7 +214,7 @@ void main() { ...@@ -213,7 +214,7 @@ void main() {
expect(m3.confirmButtonStyle.toString(), equalsIgnoringHashCodes(TextButton.styleFrom().toString())); expect(m3.confirmButtonStyle.toString(), equalsIgnoringHashCodes(TextButton.styleFrom().toString()));
}); });
testWidgets('DatePickerTheme.defaults M2 defaults', (WidgetTester tester) async { testWidgetsWithLeakTracking('DatePickerTheme.defaults M2 defaults', (WidgetTester tester) async {
late final DatePickerThemeData m2; // M2 defaults late final DatePickerThemeData m2; // M2 defaults
late final ThemeData theme; late final ThemeData theme;
late final ColorScheme colorScheme; late final ColorScheme colorScheme;
...@@ -282,7 +283,7 @@ void main() { ...@@ -282,7 +283,7 @@ void main() {
expect(m2.confirmButtonStyle.toString(), equalsIgnoringHashCodes(TextButton.styleFrom().toString())); expect(m2.confirmButtonStyle.toString(), equalsIgnoringHashCodes(TextButton.styleFrom().toString()));
}); });
testWidgets('Default DatePickerThemeData debugFillProperties', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default DatePickerThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const DatePickerThemeData().debugFillProperties(builder); const DatePickerThemeData().debugFillProperties(builder);
...@@ -294,7 +295,7 @@ void main() { ...@@ -294,7 +295,7 @@ void main() {
expect(description, <String>[]); expect(description, <String>[]);
}); });
testWidgets('DatePickerThemeData implements debugFillProperties', (WidgetTester tester) async { testWidgetsWithLeakTracking('DatePickerThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
datePickerTheme.debugFillProperties(builder); datePickerTheme.debugFillProperties(builder);
...@@ -344,7 +345,7 @@ void main() { ...@@ -344,7 +345,7 @@ void main() {
])); ]));
}); });
testWidgets('DatePickerDialog uses ThemeData datePicker theme (calendar mode)', (WidgetTester tester) async { testWidgetsWithLeakTracking('DatePickerDialog uses ThemeData datePicker theme (calendar mode)', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: ThemeData( theme: ThemeData(
...@@ -445,7 +446,7 @@ void main() { ...@@ -445,7 +446,7 @@ void main() {
expect(confirmButtonStyle.toString(), equalsIgnoringHashCodes(datePickerTheme.confirmButtonStyle.toString())); expect(confirmButtonStyle.toString(), equalsIgnoringHashCodes(datePickerTheme.confirmButtonStyle.toString()));
}); });
testWidgets('DatePickerDialog uses ThemeData datePicker theme (input mode)', (WidgetTester tester) async { testWidgetsWithLeakTracking('DatePickerDialog uses ThemeData datePicker theme (input mode)', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: ThemeData( theme: ThemeData(
...@@ -492,7 +493,7 @@ void main() { ...@@ -492,7 +493,7 @@ void main() {
expect(confirmButtonStyle.toString(), equalsIgnoringHashCodes(datePickerTheme.confirmButtonStyle.toString())); expect(confirmButtonStyle.toString(), equalsIgnoringHashCodes(datePickerTheme.confirmButtonStyle.toString()));
}); });
testWidgets('DateRangePickerDialog uses ThemeData datePicker theme', (WidgetTester tester) async { testWidgetsWithLeakTracking('DateRangePickerDialog uses ThemeData datePicker theme', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: ThemeData( theme: ThemeData(
...@@ -551,9 +552,14 @@ void main() { ...@@ -551,9 +552,14 @@ void main() {
await gesture.moveTo(tester.getCenter(find.text('18'))); await gesture.moveTo(tester.getCenter(find.text('18')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(inkFeatures, paints..circle(color: datePickerTheme.rangeSelectionOverlayColor?.resolve(<MaterialState>{}))); expect(inkFeatures, paints..circle(color: datePickerTheme.rangeSelectionOverlayColor?.resolve(<MaterialState>{})));
}); },
leakTrackingTestConfig: const LeakTrackingTestConfig(
testWidgets('Dividers use DatePickerThemeData.dividerColor', (WidgetTester tester) async { // TODO(ksokolovskyi): remove after fixing
// https://github.com/flutter/flutter/issues/136036
notDisposedAllowList: <String, int?> {'AnnotatedRegionLayer<SystemUiOverlayStyle>': 2},
));
testWidgetsWithLeakTracking('Dividers use DatePickerThemeData.dividerColor', (WidgetTester tester) async {
Future<void> showPicker(WidgetTester tester, Size size) async { Future<void> showPicker(WidgetTester tester, Size size) async {
tester.view.physicalSize = size; tester.view.physicalSize = size;
tester.view.devicePixelRatio = 1.0; tester.view.devicePixelRatio = 1.0;
...@@ -594,7 +600,7 @@ void main() { ...@@ -594,7 +600,7 @@ void main() {
expect(horizontalDivider.color, datePickerTheme.dividerColor); expect(horizontalDivider.color, datePickerTheme.dividerColor);
}); });
testWidgets( testWidgetsWithLeakTracking(
'DatePicker uses ThemeData.inputDecorationTheme properties ' 'DatePicker uses ThemeData.inputDecorationTheme properties '
'which are null in DatePickerThemeData.inputDecorationTheme', 'which are null in DatePickerThemeData.inputDecorationTheme',
(WidgetTester tester) async { (WidgetTester tester) async {
...@@ -650,7 +656,7 @@ void main() { ...@@ -650,7 +656,7 @@ void main() {
expect(inputDecoration.border , const OutlineInputBorder()); expect(inputDecoration.border , const OutlineInputBorder());
}); });
testWidgets('DatePickerDialog resolves DatePickerTheme.dayOverlayColor states', (WidgetTester tester) async { testWidgetsWithLeakTracking('DatePickerDialog resolves DatePickerTheme.dayOverlayColor states', (WidgetTester tester) async {
final MaterialStateProperty<Color> dayOverlayColor = MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { final MaterialStateProperty<Color> dayOverlayColor = MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) { if (states.contains(MaterialState.hovered)) {
return const Color(0xff00ff00); return const Color(0xff00ff00);
...@@ -742,7 +748,7 @@ void main() { ...@@ -742,7 +748,7 @@ void main() {
); );
}); });
testWidgets('DatePickerDialog resolves DatePickerTheme.yearOverlayColor states', (WidgetTester tester) async { testWidgetsWithLeakTracking('DatePickerDialog resolves DatePickerTheme.yearOverlayColor states', (WidgetTester tester) async {
final MaterialStateProperty<Color> yearOverlayColor = MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { final MaterialStateProperty<Color> yearOverlayColor = MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) { if (states.contains(MaterialState.hovered)) {
return const Color(0xff00ff00); return const Color(0xff00ff00);
...@@ -824,7 +830,7 @@ void main() { ...@@ -824,7 +830,7 @@ void main() {
); );
}); });
testWidgets('DateRangePickerDialog resolves DatePickerTheme.rangeSelectionOverlayColor states', (WidgetTester tester) async { testWidgetsWithLeakTracking('DateRangePickerDialog resolves DatePickerTheme.rangeSelectionOverlayColor states', (WidgetTester tester) async {
final MaterialStateProperty<Color> rangeSelectionOverlayColor = MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { final MaterialStateProperty<Color> rangeSelectionOverlayColor = MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) { if (states.contains(MaterialState.hovered)) {
return const Color(0xff00ff00); return const Color(0xff00ff00);
...@@ -896,5 +902,10 @@ void main() { ...@@ -896,5 +902,10 @@ void main() {
..circle(color: rangeSelectionOverlayColor.resolve(<MaterialState>{MaterialState.pressed})), ..circle(color: rangeSelectionOverlayColor.resolve(<MaterialState>{MaterialState.pressed})),
); );
} }
}); },
leakTrackingTestConfig: const LeakTrackingTestConfig(
// TODO(ksokolovskyi): remove after fixing
// https://github.com/flutter/flutter/issues/136036
notDisposedAllowList: <String, int?> {'AnnotatedRegionLayer<SystemUiOverlayStyle>': 2},
));
} }
...@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import 'feedback_tester.dart'; import 'feedback_tester.dart';
void main() { void main() {
...@@ -111,7 +112,7 @@ void main() { ...@@ -111,7 +112,7 @@ void main() {
await callback(range); await callback(range);
} }
testWidgets('Default layout (calendar mode)', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default layout (calendar mode)', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final Finder helpText = find.text('Select range'); final Finder helpText = find.text('Select range');
final Finder firstDateHeaderText = find.text('Jan 15'); final Finder firstDateHeaderText = find.text('Jan 15');
...@@ -173,7 +174,7 @@ void main() { ...@@ -173,7 +174,7 @@ void main() {
}, useMaterial3: true); }, useMaterial3: true);
}); });
testWidgets('Default Dialog properties (calendar mode)', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default Dialog properties (calendar mode)', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true); final ThemeData theme = ThemeData(useMaterial3: true);
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final Material dialogMaterial = tester.widget<Material>( final Material dialogMaterial = tester.widget<Material>(
...@@ -193,7 +194,7 @@ void main() { ...@@ -193,7 +194,7 @@ void main() {
}, useMaterial3: theme.useMaterial3); }, useMaterial3: theme.useMaterial3);
}); });
testWidgets('Default Dialog properties (input mode)', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default Dialog properties (input mode)', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true); final ThemeData theme = ThemeData(useMaterial3: true);
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final Material dialogMaterial = tester.widget<Material>( final Material dialogMaterial = tester.widget<Material>(
...@@ -213,7 +214,7 @@ void main() { ...@@ -213,7 +214,7 @@ void main() {
}, useMaterial3: theme.useMaterial3); }, useMaterial3: theme.useMaterial3);
}); });
testWidgets('Scaffold and AppBar defaults', (WidgetTester tester) async { testWidgetsWithLeakTracking('Scaffold and AppBar defaults', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true); final ThemeData theme = ThemeData(useMaterial3: true);
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final Scaffold scaffold = tester.widget<Scaffold>(find.byType(Scaffold)); final Scaffold scaffold = tester.widget<Scaffold>(find.byType(Scaffold));
...@@ -244,14 +245,14 @@ void main() { ...@@ -244,14 +245,14 @@ void main() {
await preparePicker(tester, (Future<DateTimeRange?> range) async { }, useMaterial3: true); await preparePicker(tester, (Future<DateTimeRange?> range) async { }, useMaterial3: true);
} }
testWidgets('portrait', (WidgetTester tester) async { testWidgetsWithLeakTracking('portrait', (WidgetTester tester) async {
await showPicker(tester, kCommonScreenSizePortrait); await showPicker(tester, kCommonScreenSizePortrait);
expect(tester.widget<Text>(find.text('Jan 15 – Jan 25, 2016')).style?.fontSize, 32); expect(tester.widget<Text>(find.text('Jan 15 – Jan 25, 2016')).style?.fontSize, 32);
await tester.tap(find.text('Cancel')); await tester.tap(find.text('Cancel'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
}); });
testWidgets('landscape', (WidgetTester tester) async { testWidgetsWithLeakTracking('landscape', (WidgetTester tester) async {
await showPicker(tester, kCommonScreenSizeLandscape); await showPicker(tester, kCommonScreenSizeLandscape);
expect(tester.widget<Text>(find.text('Jan 15 – Jan 25, 2016')).style?.fontSize, 24); expect(tester.widget<Text>(find.text('Jan 15 – Jan 25, 2016')).style?.fontSize, 24);
await tester.tap(find.text('Cancel')); await tester.tap(find.text('Cancel'));
...@@ -259,7 +260,7 @@ void main() { ...@@ -259,7 +260,7 @@ void main() {
}); });
}); });
testWidgets('Save and help text is used', (WidgetTester tester) async { testWidgetsWithLeakTracking('Save and help text is used', (WidgetTester tester) async {
helpText = 'help'; helpText = 'help';
saveText = 'make it so'; saveText = 'make it so';
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
...@@ -268,14 +269,14 @@ void main() { ...@@ -268,14 +269,14 @@ void main() {
}); });
}); });
testWidgets('Material3 has sentence case labels', (WidgetTester tester) async { testWidgetsWithLeakTracking('Material3 has sentence case labels', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.text('Save'), findsOneWidget); expect(find.text('Save'), findsOneWidget);
expect(find.text('Select range'), findsOneWidget); expect(find.text('Select range'), findsOneWidget);
}, useMaterial3: true); }, useMaterial3: true);
}); });
testWidgets('Initial date is the default', (WidgetTester tester) async { testWidgetsWithLeakTracking('Initial date is the default', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('SAVE')); await tester.tap(find.text('SAVE'));
expect( expect(
...@@ -288,7 +289,7 @@ void main() { ...@@ -288,7 +289,7 @@ void main() {
}); });
}); });
testWidgets('Last month header should be visible if last date is selected', (WidgetTester tester) async { testWidgetsWithLeakTracking('Last month header should be visible if last date is selected', (WidgetTester tester) async {
firstDate = DateTime(2015); firstDate = DateTime(2015);
lastDate = DateTime(2016, DateTime.december, 31); lastDate = DateTime(2016, DateTime.december, 31);
initialDateRange = DateTimeRange( initialDateRange = DateTimeRange(
...@@ -302,7 +303,7 @@ void main() { ...@@ -302,7 +303,7 @@ void main() {
}); });
}); });
testWidgets('First month header should be visible if first date is selected', (WidgetTester tester) async { testWidgetsWithLeakTracking('First month header should be visible if first date is selected', (WidgetTester tester) async {
firstDate = DateTime(2015); firstDate = DateTime(2015);
lastDate = DateTime(2016, DateTime.december, 31); lastDate = DateTime(2016, DateTime.december, 31);
initialDateRange = DateTimeRange( initialDateRange = DateTimeRange(
...@@ -317,7 +318,7 @@ void main() { ...@@ -317,7 +318,7 @@ void main() {
}); });
}); });
testWidgets('Current month header should be visible if no date is selected', (WidgetTester tester) async { testWidgetsWithLeakTracking('Current month header should be visible if no date is selected', (WidgetTester tester) async {
firstDate = DateTime(2015); firstDate = DateTime(2015);
lastDate = DateTime(2016, DateTime.december, 31); lastDate = DateTime(2016, DateTime.december, 31);
currentDate = DateTime(2016, DateTime.september); currentDate = DateTime(2016, DateTime.september);
...@@ -331,14 +332,14 @@ void main() { ...@@ -331,14 +332,14 @@ void main() {
}); });
}); });
testWidgets('Can cancel', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can cancel', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.byIcon(Icons.close)); await tester.tap(find.byIcon(Icons.close));
expect(await range, isNull); expect(await range, isNull);
}); });
}); });
testWidgets('Can select a range', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can select a range', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('12').first); await tester.tap(find.text('12').first);
await tester.tap(find.text('14').first); await tester.tap(find.text('14').first);
...@@ -350,7 +351,7 @@ void main() { ...@@ -350,7 +351,7 @@ void main() {
}); });
}); });
testWidgets('Tapping earlier date resets selected range', (WidgetTester tester) async { testWidgetsWithLeakTracking('Tapping earlier date resets selected range', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('12').first); await tester.tap(find.text('12').first);
await tester.tap(find.text('11').first); await tester.tap(find.text('11').first);
...@@ -363,7 +364,7 @@ void main() { ...@@ -363,7 +364,7 @@ void main() {
}); });
}); });
testWidgets('Can select single day range', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can select single day range', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('12').first); await tester.tap(find.text('12').first);
await tester.tap(find.text('12').first); await tester.tap(find.text('12').first);
...@@ -375,7 +376,7 @@ void main() { ...@@ -375,7 +376,7 @@ void main() {
}); });
}); });
testWidgets('Cannot select a day outside bounds', (WidgetTester tester) async { testWidgetsWithLeakTracking('Cannot select a day outside bounds', (WidgetTester tester) async {
initialDateRange = DateTimeRange( initialDateRange = DateTimeRange(
start: DateTime(2017, DateTime.january, 13), start: DateTime(2017, DateTime.january, 13),
end: DateTime(2017, DateTime.january, 15), end: DateTime(2017, DateTime.january, 15),
...@@ -393,7 +394,7 @@ void main() { ...@@ -393,7 +394,7 @@ void main() {
}); });
}); });
testWidgets('Can switch from calendar to input entry mode', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can switch from calendar to input entry mode', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNothing); expect(find.byType(TextField), findsNothing);
await tester.tap(find.byIcon(Icons.edit)); await tester.tap(find.byIcon(Icons.edit));
...@@ -402,7 +403,7 @@ void main() { ...@@ -402,7 +403,7 @@ void main() {
}); });
}); });
testWidgets('Can switch from input to calendar entry mode', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can switch from input to calendar entry mode', (WidgetTester tester) async {
initialEntryMode = DatePickerEntryMode.input; initialEntryMode = DatePickerEntryMode.input;
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNWidgets(2)); expect(find.byType(TextField), findsNWidgets(2));
...@@ -412,7 +413,7 @@ void main() { ...@@ -412,7 +413,7 @@ void main() {
}); });
}); });
testWidgets('Can not switch out of calendarOnly mode', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can not switch out of calendarOnly mode', (WidgetTester tester) async {
initialEntryMode = DatePickerEntryMode.calendarOnly; initialEntryMode = DatePickerEntryMode.calendarOnly;
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNothing); expect(find.byType(TextField), findsNothing);
...@@ -420,7 +421,7 @@ void main() { ...@@ -420,7 +421,7 @@ void main() {
}); });
}); });
testWidgets('Can not switch out of inputOnly mode', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can not switch out of inputOnly mode', (WidgetTester tester) async {
initialEntryMode = DatePickerEntryMode.inputOnly; initialEntryMode = DatePickerEntryMode.inputOnly;
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNWidgets(2)); expect(find.byType(TextField), findsNWidgets(2));
...@@ -428,7 +429,7 @@ void main() { ...@@ -428,7 +429,7 @@ void main() {
}); });
}); });
testWidgets('Input only mode should validate date', (WidgetTester tester) async { testWidgetsWithLeakTracking('Input only mode should validate date', (WidgetTester tester) async {
initialEntryMode = DatePickerEntryMode.inputOnly; initialEntryMode = DatePickerEntryMode.inputOnly;
errorInvalidText = 'oops'; errorInvalidText = 'oops';
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
...@@ -442,7 +443,7 @@ void main() { ...@@ -442,7 +443,7 @@ void main() {
}); });
}); });
testWidgets('Switching to input mode keeps selected date', (WidgetTester tester) async { testWidgetsWithLeakTracking('Switching to input mode keeps selected date', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('12').first); await tester.tap(find.text('12').first);
await tester.tap(find.text('14').first); await tester.tap(find.text('14').first);
...@@ -461,7 +462,7 @@ void main() { ...@@ -461,7 +462,7 @@ void main() {
initialEntryMode = DatePickerEntryMode.input; initialEntryMode = DatePickerEntryMode.input;
}); });
testWidgets('Invalid start date', (WidgetTester tester) async { testWidgetsWithLeakTracking('Invalid start date', (WidgetTester tester) async {
// Invalid start date should have neither a start nor end date selected in // Invalid start date should have neither a start nor end date selected in
// calendar mode // calendar mode
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
...@@ -475,7 +476,7 @@ void main() { ...@@ -475,7 +476,7 @@ void main() {
}); });
}); });
testWidgets('Invalid end date', (WidgetTester tester) async { testWidgetsWithLeakTracking('Invalid end date', (WidgetTester tester) async {
// Invalid end date should only have a start date selected // Invalid end date should only have a start date selected
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/24/2016'); await tester.enterText(find.byType(TextField).at(0), '12/24/2016');
...@@ -488,7 +489,7 @@ void main() { ...@@ -488,7 +489,7 @@ void main() {
}); });
}); });
testWidgets('Invalid range', (WidgetTester tester) async { testWidgetsWithLeakTracking('Invalid range', (WidgetTester tester) async {
// Start date after end date should just use the start date // Start date after end date should just use the start date
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/25/2016'); await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
...@@ -502,7 +503,7 @@ void main() { ...@@ -502,7 +503,7 @@ void main() {
}); });
}); });
testWidgets('OK Cancel button layout', (WidgetTester tester) async { testWidgetsWithLeakTracking('OK Cancel button layout', (WidgetTester tester) async {
Widget buildFrame(TextDirection textDirection) { Widget buildFrame(TextDirection textDirection) {
return MaterialApp( return MaterialApp(
theme: ThemeData(useMaterial3: false), theme: ThemeData(useMaterial3: false),
...@@ -577,7 +578,7 @@ void main() { ...@@ -577,7 +578,7 @@ void main() {
feedback.dispose(); feedback.dispose();
}); });
testWidgets('Selecting dates vibrates', (WidgetTester tester) async { testWidgetsWithLeakTracking('Selecting dates vibrates', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('10').first); await tester.tap(find.text('10').first);
await tester.pump(hapticFeedbackInterval); await tester.pump(hapticFeedbackInterval);
...@@ -591,7 +592,7 @@ void main() { ...@@ -591,7 +592,7 @@ void main() {
}); });
}); });
testWidgets('Tapping unselectable date does not vibrate', (WidgetTester tester) async { testWidgetsWithLeakTracking('Tapping unselectable date does not vibrate', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('8').first); await tester.tap(find.text('8').first);
await tester.pump(hapticFeedbackInterval); await tester.pump(hapticFeedbackInterval);
...@@ -601,7 +602,7 @@ void main() { ...@@ -601,7 +602,7 @@ void main() {
}); });
group('Keyboard navigation', () { group('Keyboard navigation', () {
testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can toggle to calendar entry mode', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNothing); expect(find.byType(TextField), findsNothing);
// Navigate to the entry toggle button and activate it // Navigate to the entry toggle button and activate it
...@@ -614,7 +615,7 @@ void main() { ...@@ -614,7 +615,7 @@ void main() {
}); });
}); });
testWidgets('Can navigate date grid with arrow keys', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can navigate date grid with arrow keys', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
// Navigate to the grid // Navigate to the grid
await tester.sendKeyEvent(LogicalKeyboardKey.tab); await tester.sendKeyEvent(LogicalKeyboardKey.tab);
...@@ -666,7 +667,7 @@ void main() { ...@@ -666,7 +667,7 @@ void main() {
}); });
}); });
testWidgets('Navigating with arrow keys scrolls as needed', (WidgetTester tester) async { testWidgetsWithLeakTracking('Navigating with arrow keys scrolls as needed', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
// Jan and Feb headers should be showing, but no March // Jan and Feb headers should be showing, but no March
expect(find.text('January 2016'), findsOneWidget); expect(find.text('January 2016'), findsOneWidget);
...@@ -731,7 +732,7 @@ void main() { ...@@ -731,7 +732,7 @@ void main() {
}); });
}); });
testWidgets('RTL text direction reverses the horizontal arrow key navigation', (WidgetTester tester) async { testWidgetsWithLeakTracking('RTL text direction reverses the horizontal arrow key navigation', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
// Navigate to the grid // Navigate to the grid
await tester.sendKeyEvent(LogicalKeyboardKey.tab); await tester.sendKeyEvent(LogicalKeyboardKey.tab);
...@@ -790,7 +791,7 @@ void main() { ...@@ -790,7 +791,7 @@ void main() {
initialEntryMode = DatePickerEntryMode.input; initialEntryMode = DatePickerEntryMode.input;
}); });
testWidgets('Default Dialog properties (input mode)', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default Dialog properties (input mode)', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true); final ThemeData theme = ThemeData(useMaterial3: true);
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final Material dialogMaterial = tester.widget<Material>( final Material dialogMaterial = tester.widget<Material>(
...@@ -813,7 +814,7 @@ void main() { ...@@ -813,7 +814,7 @@ void main() {
}, useMaterial3: theme.useMaterial3); }, useMaterial3: theme.useMaterial3);
}); });
testWidgets('Default InputDecoration', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default InputDecoration', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final InputDecoration startDateDecoration = tester.widget<TextField>( final InputDecoration startDateDecoration = tester.widget<TextField>(
find.byType(TextField).first).decoration!; find.byType(TextField).first).decoration!;
...@@ -833,13 +834,13 @@ void main() { ...@@ -833,13 +834,13 @@ void main() {
}, useMaterial3: true); }, useMaterial3: true);
}); });
testWidgets('Initial entry mode is used', (WidgetTester tester) async { testWidgetsWithLeakTracking('Initial entry mode is used', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNWidgets(2)); expect(find.byType(TextField), findsNWidgets(2));
}); });
}); });
testWidgets('All custom strings are used', (WidgetTester tester) async { testWidgetsWithLeakTracking('All custom strings are used', (WidgetTester tester) async {
initialDateRange = null; initialDateRange = null;
cancelText = 'nope'; cancelText = 'nope';
confirmText = 'yep'; confirmText = 'yep';
...@@ -859,7 +860,7 @@ void main() { ...@@ -859,7 +860,7 @@ void main() {
}); });
}); });
testWidgets('Initial date is the default', (WidgetTester tester) async { testWidgetsWithLeakTracking('Initial date is the default', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('OK')); await tester.tap(find.text('OK'));
expect(await range, DateTimeRange( expect(await range, DateTimeRange(
...@@ -869,7 +870,7 @@ void main() { ...@@ -869,7 +870,7 @@ void main() {
}); });
}); });
testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async { testWidgetsWithLeakTracking('Can toggle to calendar entry mode', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNWidgets(2)); expect(find.byType(TextField), findsNWidgets(2));
await tester.tap(find.byIcon(Icons.calendar_today)); await tester.tap(find.byIcon(Icons.calendar_today));
...@@ -878,7 +879,7 @@ void main() { ...@@ -878,7 +879,7 @@ void main() {
}); });
}); });
testWidgets('Toggle to calendar mode keeps selected date', (WidgetTester tester) async { testWidgetsWithLeakTracking('Toggle to calendar mode keeps selected date', (WidgetTester tester) async {
initialDateRange = null; initialDateRange = null;
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/25/2016'); await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
...@@ -894,7 +895,7 @@ void main() { ...@@ -894,7 +895,7 @@ void main() {
}); });
}); });
testWidgets('Entered text returns range', (WidgetTester tester) async { testWidgetsWithLeakTracking('Entered text returns range', (WidgetTester tester) async {
initialDateRange = null; initialDateRange = null;
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/25/2016'); await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
...@@ -908,7 +909,7 @@ void main() { ...@@ -908,7 +909,7 @@ void main() {
}); });
}); });
testWidgets('Too short entered text shows error', (WidgetTester tester) async { testWidgetsWithLeakTracking('Too short entered text shows error', (WidgetTester tester) async {
initialDateRange = null; initialDateRange = null;
errorFormatText = 'oops'; errorFormatText = 'oops';
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
...@@ -922,7 +923,7 @@ void main() { ...@@ -922,7 +923,7 @@ void main() {
}); });
}); });
testWidgets('Bad format entered text shows error', (WidgetTester tester) async { testWidgetsWithLeakTracking('Bad format entered text shows error', (WidgetTester tester) async {
initialDateRange = null; initialDateRange = null;
errorFormatText = 'oops'; errorFormatText = 'oops';
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
...@@ -936,7 +937,7 @@ void main() { ...@@ -936,7 +937,7 @@ void main() {
}); });
}); });
testWidgets('Invalid entered text shows error', (WidgetTester tester) async { testWidgetsWithLeakTracking('Invalid entered text shows error', (WidgetTester tester) async {
initialDateRange = null; initialDateRange = null;
errorInvalidText = 'oops'; errorInvalidText = 'oops';
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
...@@ -950,7 +951,7 @@ void main() { ...@@ -950,7 +951,7 @@ void main() {
}); });
}); });
testWidgets('End before start date shows error', (WidgetTester tester) async { testWidgetsWithLeakTracking('End before start date shows error', (WidgetTester tester) async {
initialDateRange = null; initialDateRange = null;
errorInvalidRangeText = 'oops'; errorInvalidRangeText = 'oops';
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
...@@ -964,7 +965,7 @@ void main() { ...@@ -964,7 +965,7 @@ void main() {
}); });
}); });
testWidgets('Error text only displayed for invalid date', (WidgetTester tester) async { testWidgetsWithLeakTracking('Error text only displayed for invalid date', (WidgetTester tester) async {
initialDateRange = null; initialDateRange = null;
errorInvalidText = 'oops'; errorInvalidText = 'oops';
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
...@@ -978,7 +979,7 @@ void main() { ...@@ -978,7 +979,7 @@ void main() {
}); });
}); });
testWidgets('End before start date does not get passed to calendar mode', (WidgetTester tester) async { testWidgetsWithLeakTracking('End before start date does not get passed to calendar mode', (WidgetTester tester) async {
initialDateRange = null; initialDateRange = null;
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/27/2016'); await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
...@@ -996,7 +997,7 @@ void main() { ...@@ -996,7 +997,7 @@ void main() {
}); });
}); });
testWidgets('InputDecorationTheme is honored', (WidgetTester tester) async { testWidgetsWithLeakTracking('InputDecorationTheme is honored', (WidgetTester tester) async {
// Given a custom paint for an input decoration, extract the border and // Given a custom paint for an input decoration, extract the border and
// fill color and test them against the expected values. // fill color and test them against the expected values.
...@@ -1062,7 +1063,7 @@ void main() { ...@@ -1062,7 +1063,7 @@ void main() {
}); });
// This is a regression test for https://github.com/flutter/flutter/issues/131989. // This is a regression test for https://github.com/flutter/flutter/issues/131989.
testWidgets('Dialog contents do not overflow when resized from landscape to portrait', testWidgetsWithLeakTracking('Dialog contents do not overflow when resized from landscape to portrait',
(WidgetTester tester) async { (WidgetTester tester) async {
addTearDown(tester.view.reset); addTearDown(tester.view.reset);
// Initial window size is wide for landscape mode. // Initial window size is wide for landscape mode.
...@@ -1078,7 +1079,7 @@ void main() { ...@@ -1078,7 +1079,7 @@ void main() {
}); });
}); });
testWidgets('DatePickerDialog is state restorable', (WidgetTester tester) async { testWidgetsWithLeakTracking('DatePickerDialog is state restorable', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: ThemeData(useMaterial3: false), theme: ThemeData(useMaterial3: false),
...@@ -1134,7 +1135,7 @@ void main() { ...@@ -1134,7 +1135,7 @@ void main() {
expect(find.text('12/1/2021 to 14/1/2021'), findsOneWidget); expect(find.text('12/1/2021 to 14/1/2021'), findsOneWidget);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
testWidgets('DateRangePickerDialog state restoration - DatePickerEntryMode', (WidgetTester tester) async { testWidgetsWithLeakTracking('DateRangePickerDialog state restoration - DatePickerEntryMode', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( const MaterialApp(
restorationScopeId: 'app', restorationScopeId: 'app',
...@@ -1184,7 +1185,7 @@ void main() { ...@@ -1184,7 +1185,7 @@ void main() {
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
group('showDateRangePicker avoids overlapping display features', () { group('showDateRangePicker avoids overlapping display features', () {
testWidgets('positioning with anchorPoint', (WidgetTester tester) async { testWidgetsWithLeakTracking('positioning with anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
builder: (BuildContext context, Widget? child) { builder: (BuildContext context, Widget? child) {
...@@ -1221,7 +1222,7 @@ void main() { ...@@ -1221,7 +1222,7 @@ void main() {
expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), const Offset(800.0, 600.0)); expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), const Offset(800.0, 600.0));
}); });
testWidgets('positioning with Directionality', (WidgetTester tester) async { testWidgetsWithLeakTracking('positioning with Directionality', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
builder: (BuildContext context, Widget? child) { builder: (BuildContext context, Widget? child) {
...@@ -1261,7 +1262,7 @@ void main() { ...@@ -1261,7 +1262,7 @@ void main() {
expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), const Offset(800.0, 600.0)); expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), const Offset(800.0, 600.0));
}); });
testWidgets('positioning with defaults', (WidgetTester tester) async { testWidgetsWithLeakTracking('positioning with defaults', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
builder: (BuildContext context, Widget? child) { builder: (BuildContext context, Widget? child) {
...@@ -1299,7 +1300,7 @@ void main() { ...@@ -1299,7 +1300,7 @@ void main() {
}); });
group('Semantics', () { group('Semantics', () {
testWidgets('calendar mode', (WidgetTester tester) async { testWidgetsWithLeakTracking('calendar mode', (WidgetTester tester) async {
final SemanticsHandle semantics = tester.ensureSemantics(); final SemanticsHandle semantics = tester.ensureSemantics();
currentDate = DateTime(2016, DateTime.january, 30); currentDate = DateTime(2016, DateTime.january, 30);
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
...@@ -1317,7 +1318,7 @@ void main() { ...@@ -1317,7 +1318,7 @@ void main() {
}); });
for (final TextInputType? keyboardType in <TextInputType?>[null, TextInputType.emailAddress]) { for (final TextInputType? keyboardType in <TextInputType?>[null, TextInputType.emailAddress]) {
testWidgets('DateRangePicker takes keyboardType $keyboardType', (WidgetTester tester) async { testWidgetsWithLeakTracking('DateRangePicker takes keyboardType $keyboardType', (WidgetTester tester) async {
late BuildContext buttonContext; late BuildContext buttonContext;
const InputBorder border = InputBorder.none; const InputBorder border = InputBorder.none;
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
...@@ -1370,7 +1371,7 @@ void main() { ...@@ -1370,7 +1371,7 @@ void main() {
}); });
} }
testWidgets('honors switchToInputEntryModeIcon', (WidgetTester tester) async { testWidgetsWithLeakTracking('honors switchToInputEntryModeIcon', (WidgetTester tester) async {
Widget buildApp({bool? useMaterial3, Icon? switchToInputEntryModeIcon}) { Widget buildApp({bool? useMaterial3, Icon? switchToInputEntryModeIcon}) {
return MaterialApp( return MaterialApp(
theme: ThemeData( theme: ThemeData(
...@@ -1425,7 +1426,7 @@ void main() { ...@@ -1425,7 +1426,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
}); });
testWidgets('honors switchToCalendarEntryModeIcon', (WidgetTester tester) async { testWidgetsWithLeakTracking('honors switchToCalendarEntryModeIcon', (WidgetTester tester) async {
Widget buildApp({bool? useMaterial3, Icon? switchToCalendarEntryModeIcon}) { Widget buildApp({bool? useMaterial3, Icon? switchToCalendarEntryModeIcon}) {
return MaterialApp( return MaterialApp(
theme: ThemeData( theme: ThemeData(
...@@ -1488,7 +1489,7 @@ void main() { ...@@ -1488,7 +1489,7 @@ void main() {
// support is deprecated and the APIs are removed, these tests // support is deprecated and the APIs are removed, these tests
// can be deleted. // can be deleted.
testWidgets('Default layout (calendar mode)', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default layout (calendar mode)', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final Finder helpText = find.text('SELECT RANGE'); final Finder helpText = find.text('SELECT RANGE');
final Finder firstDateHeaderText = find.text('Jan 15'); final Finder firstDateHeaderText = find.text('Jan 15');
...@@ -1544,7 +1545,7 @@ void main() { ...@@ -1544,7 +1545,7 @@ void main() {
}); });
}); });
testWidgets('Default Dialog properties (calendar mode)', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default Dialog properties (calendar mode)', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: false); final ThemeData theme = ThemeData(useMaterial3: false);
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final Material dialogMaterial = tester.widget<Material>( final Material dialogMaterial = tester.widget<Material>(
...@@ -1564,7 +1565,7 @@ void main() { ...@@ -1564,7 +1565,7 @@ void main() {
}); });
}); });
testWidgets('Scaffold and AppBar defaults', (WidgetTester tester) async { testWidgetsWithLeakTracking('Scaffold and AppBar defaults', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: false); final ThemeData theme = ThemeData(useMaterial3: false);
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final Scaffold scaffold = tester.widget<Scaffold>(find.byType(Scaffold)); final Scaffold scaffold = tester.widget<Scaffold>(find.byType(Scaffold));
...@@ -1591,7 +1592,7 @@ void main() { ...@@ -1591,7 +1592,7 @@ void main() {
initialEntryMode = DatePickerEntryMode.input; initialEntryMode = DatePickerEntryMode.input;
}); });
testWidgets('Default Dialog properties (input mode)', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default Dialog properties (input mode)', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: false); final ThemeData theme = ThemeData(useMaterial3: false);
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final Material dialogMaterial = tester.widget<Material>( final Material dialogMaterial = tester.widget<Material>(
...@@ -1614,7 +1615,7 @@ void main() { ...@@ -1614,7 +1615,7 @@ void main() {
}); });
}); });
testWidgets('Default InputDecoration', (WidgetTester tester) async { testWidgetsWithLeakTracking('Default InputDecoration', (WidgetTester tester) async {
await preparePicker(tester, (Future<DateTimeRange?> range) async { await preparePicker(tester, (Future<DateTimeRange?> range) async {
final InputDecoration startDateDecoration = tester.widget<TextField>( final InputDecoration startDateDecoration = tester.widget<TextField>(
find.byType(TextField).first).decoration!; find.byType(TextField).first).decoration!;
...@@ -1666,6 +1667,14 @@ class _RestorableDateRangePickerDialogTestWidgetState extends State<_RestorableD ...@@ -1666,6 +1667,14 @@ class _RestorableDateRangePickerDialogTestWidgetState extends State<_RestorableD
}, },
); );
@override
void dispose() {
_startDate.dispose();
_endDate.dispose();
_restorableDateRangePickerRouteFuture.dispose();
super.dispose();
}
@override @override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) { void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
registerForRestoration(_startDate, 'start_date'); registerForRestoration(_startDate, 'start_date');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment