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`.
This commit is contained in:
parent
670e6ba129
commit
d76e3abf10
@ -1353,6 +1353,15 @@ class _DateRangePickerDialogState extends State<DateRangePickerDialog> with Rest
|
||||
registerForRestoration(_autoValidate, 'autovalidate');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_entryMode.dispose();
|
||||
_selectedStart.dispose();
|
||||
_selectedEnd.dispose();
|
||||
_autoValidate.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleOk() {
|
||||
if (_entryMode.value == DatePickerEntryMode.input || _entryMode.value == DatePickerEntryMode.inputOnly) {
|
||||
final _InputDateRangePickerState picker = _inputPickerKey.currentState!;
|
||||
@ -2368,143 +2377,32 @@ class _MonthItemState extends State<_MonthItem> {
|
||||
}
|
||||
|
||||
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 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 isSelectedDayStart = widget.selectedDateStart != null && dayToBuild.isAtSameMomentAs(widget.selectedDateStart!);
|
||||
final bool isSelectedDayEnd = widget.selectedDateEnd != null && dayToBuild.isAtSameMomentAs(widget.selectedDateEnd!);
|
||||
final bool isInRange = isRangeSelected &&
|
||||
dayToBuild.isAfter(widget.selectedDateStart!) &&
|
||||
dayToBuild.isBefore(widget.selectedDateEnd!);
|
||||
final bool isOneDayRange = isRangeSelected && widget.selectedDateStart == widget.selectedDateEnd;
|
||||
final bool isToday = DateUtils.isSameDay(widget.currentDate, dayToBuild);
|
||||
|
||||
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 (isDisabled) MaterialState.disabled,
|
||||
if (isSelectedDayStart || isSelectedDayEnd) MaterialState.selected,
|
||||
};
|
||||
|
||||
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),
|
||||
)
|
||||
return _DayItem(
|
||||
day: dayToBuild,
|
||||
focusNode: _dayFocusNodes[day - 1],
|
||||
onChanged: widget.onChanged,
|
||||
onFocusChange: _dayFocusChanged,
|
||||
highlightColor: _highlightColor(context),
|
||||
isDisabled: isDisabled,
|
||||
isRangeSelected: isRangeSelected,
|
||||
isSelectedDayStart: isSelectedDayStart,
|
||||
isSelectedDayEnd: isSelectedDayEnd,
|
||||
isInRange: isInRange,
|
||||
isOneDayRange: isOneDayRange,
|
||||
isToday: isToday,
|
||||
);
|
||||
|
||||
_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) {
|
||||
@ -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.
|
||||
enum _HighlightPainterStyle {
|
||||
/// Paints nothing.
|
||||
|
@ -7,6 +7,7 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
||||
|
||||
void main() {
|
||||
const DatePickerThemeData datePickerTheme = DatePickerThemeData(
|
||||
@ -136,7 +137,7 @@ void main() {
|
||||
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 ThemeData theme;
|
||||
late final ColorScheme colorScheme;
|
||||
@ -213,7 +214,7 @@ void main() {
|
||||
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 ThemeData theme;
|
||||
late final ColorScheme colorScheme;
|
||||
@ -282,7 +283,7 @@ void main() {
|
||||
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();
|
||||
const DatePickerThemeData().debugFillProperties(builder);
|
||||
|
||||
@ -294,7 +295,7 @@ void main() {
|
||||
expect(description, <String>[]);
|
||||
});
|
||||
|
||||
testWidgets('DatePickerThemeData implements debugFillProperties', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('DatePickerThemeData implements debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
|
||||
datePickerTheme.debugFillProperties(builder);
|
||||
@ -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(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
@ -445,7 +446,7 @@ void main() {
|
||||
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(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
@ -492,7 +493,7 @@ void main() {
|
||||
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(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
@ -551,9 +552,14 @@ void main() {
|
||||
await gesture.moveTo(tester.getCenter(find.text('18')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(inkFeatures, paints..circle(color: datePickerTheme.rangeSelectionOverlayColor?.resolve(<MaterialState>{})));
|
||||
});
|
||||
},
|
||||
leakTrackingTestConfig: const LeakTrackingTestConfig(
|
||||
// TODO(ksokolovskyi): remove after fixing
|
||||
// https://github.com/flutter/flutter/issues/136036
|
||||
notDisposedAllowList: <String, int?> {'AnnotatedRegionLayer<SystemUiOverlayStyle>': 2},
|
||||
));
|
||||
|
||||
testWidgets('Dividers use DatePickerThemeData.dividerColor', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('Dividers use DatePickerThemeData.dividerColor', (WidgetTester tester) async {
|
||||
Future<void> showPicker(WidgetTester tester, Size size) async {
|
||||
tester.view.physicalSize = size;
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
@ -594,7 +600,7 @@ void main() {
|
||||
expect(horizontalDivider.color, datePickerTheme.dividerColor);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
testWidgetsWithLeakTracking(
|
||||
'DatePicker uses ThemeData.inputDecorationTheme properties '
|
||||
'which are null in DatePickerThemeData.inputDecorationTheme',
|
||||
(WidgetTester tester) async {
|
||||
@ -650,7 +656,7 @@ void main() {
|
||||
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) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return const Color(0xff00ff00);
|
||||
@ -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) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return const Color(0xff00ff00);
|
||||
@ -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) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return const Color(0xff00ff00);
|
||||
@ -896,5 +902,10 @@ void main() {
|
||||
..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';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
||||
import 'feedback_tester.dart';
|
||||
|
||||
void main() {
|
||||
@ -111,7 +112,7 @@ void main() {
|
||||
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 {
|
||||
final Finder helpText = find.text('Select range');
|
||||
final Finder firstDateHeaderText = find.text('Jan 15');
|
||||
@ -173,7 +174,7 @@ void main() {
|
||||
}, 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);
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
final Material dialogMaterial = tester.widget<Material>(
|
||||
@ -193,7 +194,7 @@ void main() {
|
||||
}, 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);
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
final Material dialogMaterial = tester.widget<Material>(
|
||||
@ -213,7 +214,7 @@ void main() {
|
||||
}, 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);
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
final Scaffold scaffold = tester.widget<Scaffold>(find.byType(Scaffold));
|
||||
@ -244,14 +245,14 @@ void main() {
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async { }, useMaterial3: true);
|
||||
}
|
||||
|
||||
testWidgets('portrait', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('portrait', (WidgetTester tester) async {
|
||||
await showPicker(tester, kCommonScreenSizePortrait);
|
||||
expect(tester.widget<Text>(find.text('Jan 15 – Jan 25, 2016')).style?.fontSize, 32);
|
||||
await tester.tap(find.text('Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('landscape', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('landscape', (WidgetTester tester) async {
|
||||
await showPicker(tester, kCommonScreenSizeLandscape);
|
||||
expect(tester.widget<Text>(find.text('Jan 15 – Jan 25, 2016')).style?.fontSize, 24);
|
||||
await tester.tap(find.text('Cancel'));
|
||||
@ -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';
|
||||
saveText = 'make it so';
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
@ -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 {
|
||||
expect(find.text('Save'), findsOneWidget);
|
||||
expect(find.text('Select range'), findsOneWidget);
|
||||
}, 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 tester.tap(find.text('SAVE'));
|
||||
expect(
|
||||
@ -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);
|
||||
lastDate = DateTime(2016, DateTime.december, 31);
|
||||
initialDateRange = DateTimeRange(
|
||||
@ -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);
|
||||
lastDate = DateTime(2016, DateTime.december, 31);
|
||||
initialDateRange = DateTimeRange(
|
||||
@ -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);
|
||||
lastDate = DateTime(2016, DateTime.december, 31);
|
||||
currentDate = DateTime(2016, DateTime.september);
|
||||
@ -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 tester.tap(find.byIcon(Icons.close));
|
||||
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 tester.tap(find.text('12').first);
|
||||
await tester.tap(find.text('14').first);
|
||||
@ -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 tester.tap(find.text('12').first);
|
||||
await tester.tap(find.text('11').first);
|
||||
@ -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 tester.tap(find.text('12').first);
|
||||
await tester.tap(find.text('12').first);
|
||||
@ -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(
|
||||
start: DateTime(2017, DateTime.january, 13),
|
||||
end: DateTime(2017, DateTime.january, 15),
|
||||
@ -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 {
|
||||
expect(find.byType(TextField), findsNothing);
|
||||
await tester.tap(find.byIcon(Icons.edit));
|
||||
@ -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;
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
expect(find.byType(TextField), findsNWidgets(2));
|
||||
@ -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;
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
expect(find.byType(TextField), findsNothing);
|
||||
@ -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;
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
expect(find.byType(TextField), findsNWidgets(2));
|
||||
@ -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;
|
||||
errorInvalidText = 'oops';
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
@ -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 tester.tap(find.text('12').first);
|
||||
await tester.tap(find.text('14').first);
|
||||
@ -461,7 +462,7 @@ void main() {
|
||||
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
|
||||
// calendar mode
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
@ -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
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
await tester.enterText(find.byType(TextField).at(0), '12/24/2016');
|
||||
@ -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
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
|
||||
@ -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) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(useMaterial3: false),
|
||||
@ -577,7 +578,7 @@ void main() {
|
||||
feedback.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Selecting dates vibrates', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('Selecting dates vibrates', (WidgetTester tester) async {
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
await tester.tap(find.text('10').first);
|
||||
await tester.pump(hapticFeedbackInterval);
|
||||
@ -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 tester.tap(find.text('8').first);
|
||||
await tester.pump(hapticFeedbackInterval);
|
||||
@ -601,7 +602,7 @@ void main() {
|
||||
});
|
||||
|
||||
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 {
|
||||
expect(find.byType(TextField), findsNothing);
|
||||
// Navigate to the entry toggle button and activate it
|
||||
@ -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 {
|
||||
// Navigate to the grid
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||
@ -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 {
|
||||
// Jan and Feb headers should be showing, but no March
|
||||
expect(find.text('January 2016'), findsOneWidget);
|
||||
@ -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 {
|
||||
// Navigate to the grid
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||
@ -790,7 +791,7 @@ void main() {
|
||||
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);
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
final Material dialogMaterial = tester.widget<Material>(
|
||||
@ -813,7 +814,7 @@ void main() {
|
||||
}, useMaterial3: theme.useMaterial3);
|
||||
});
|
||||
|
||||
testWidgets('Default InputDecoration', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('Default InputDecoration', (WidgetTester tester) async {
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
final InputDecoration startDateDecoration = tester.widget<TextField>(
|
||||
find.byType(TextField).first).decoration!;
|
||||
@ -833,13 +834,13 @@ void main() {
|
||||
}, 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 {
|
||||
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;
|
||||
cancelText = 'nope';
|
||||
confirmText = 'yep';
|
||||
@ -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 tester.tap(find.text('OK'));
|
||||
expect(await range, DateTimeRange(
|
||||
@ -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 {
|
||||
expect(find.byType(TextField), findsNWidgets(2));
|
||||
await tester.tap(find.byIcon(Icons.calendar_today));
|
||||
@ -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;
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
|
||||
@ -894,7 +895,7 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Entered text returns range', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('Entered text returns range', (WidgetTester tester) async {
|
||||
initialDateRange = null;
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
|
||||
@ -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;
|
||||
errorFormatText = 'oops';
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
@ -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;
|
||||
errorFormatText = 'oops';
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
@ -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;
|
||||
errorInvalidText = 'oops';
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
@ -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;
|
||||
errorInvalidRangeText = 'oops';
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
@ -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;
|
||||
errorInvalidText = 'oops';
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
@ -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;
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
|
||||
@ -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
|
||||
// fill color and test them against the expected values.
|
||||
@ -1062,7 +1063,7 @@ void main() {
|
||||
});
|
||||
|
||||
// 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 {
|
||||
addTearDown(tester.view.reset);
|
||||
// Initial window size is wide for landscape mode.
|
||||
@ -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(
|
||||
MaterialApp(
|
||||
theme: ThemeData(useMaterial3: false),
|
||||
@ -1134,7 +1135,7 @@ void main() {
|
||||
expect(find.text('12/1/2021 to 14/1/2021'), findsOneWidget);
|
||||
}, 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(
|
||||
const MaterialApp(
|
||||
restorationScopeId: 'app',
|
||||
@ -1184,7 +1185,7 @@ void main() {
|
||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
||||
|
||||
group('showDateRangePicker avoids overlapping display features', () {
|
||||
testWidgets('positioning with anchorPoint', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('positioning with anchorPoint', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
@ -1221,7 +1222,7 @@ void main() {
|
||||
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(
|
||||
MaterialApp(
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
@ -1261,7 +1262,7 @@ void main() {
|
||||
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(
|
||||
MaterialApp(
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
@ -1299,7 +1300,7 @@ void main() {
|
||||
});
|
||||
|
||||
group('Semantics', () {
|
||||
testWidgets('calendar mode', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('calendar mode', (WidgetTester tester) async {
|
||||
final SemanticsHandle semantics = tester.ensureSemantics();
|
||||
currentDate = DateTime(2016, DateTime.january, 30);
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
@ -1317,7 +1318,7 @@ void main() {
|
||||
});
|
||||
|
||||
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;
|
||||
const InputBorder border = InputBorder.none;
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
@ -1370,7 +1371,7 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets('honors switchToInputEntryModeIcon', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('honors switchToInputEntryModeIcon', (WidgetTester tester) async {
|
||||
Widget buildApp({bool? useMaterial3, Icon? switchToInputEntryModeIcon}) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
@ -1425,7 +1426,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('honors switchToCalendarEntryModeIcon', (WidgetTester tester) async {
|
||||
testWidgetsWithLeakTracking('honors switchToCalendarEntryModeIcon', (WidgetTester tester) async {
|
||||
Widget buildApp({bool? useMaterial3, Icon? switchToCalendarEntryModeIcon}) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
@ -1488,7 +1489,7 @@ void main() {
|
||||
// support is deprecated and the APIs are removed, these tests
|
||||
// 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 {
|
||||
final Finder helpText = find.text('SELECT RANGE');
|
||||
final Finder firstDateHeaderText = find.text('Jan 15');
|
||||
@ -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);
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
final Material dialogMaterial = tester.widget<Material>(
|
||||
@ -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);
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
final Scaffold scaffold = tester.widget<Scaffold>(find.byType(Scaffold));
|
||||
@ -1591,7 +1592,7 @@ void main() {
|
||||
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);
|
||||
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
||||
final Material dialogMaterial = tester.widget<Material>(
|
||||
@ -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 {
|
||||
final InputDecoration startDateDecoration = tester.widget<TextField>(
|
||||
find.byType(TextField).first).decoration!;
|
||||
@ -1666,6 +1667,14 @@ class _RestorableDateRangePickerDialogTestWidgetState extends State<_RestorableD
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_startDate.dispose();
|
||||
_endDate.dispose();
|
||||
_restorableDateRangePickerRouteFuture.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
||||
registerForRestoration(_startDate, 'start_date');
|
||||
|
Loading…
x
Reference in New Issue
Block a user