blob: 10158a3276ad39aa70c8bb2d8059b533a2eb0e63 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('TimePickerThemeData copyWith, ==, hashCode basics', () {
expect(const TimePickerThemeData(), const TimePickerThemeData().copyWith());
expect(const TimePickerThemeData().hashCode, const TimePickerThemeData().copyWith().hashCode);
});
test('TimePickerThemeData lerp special cases', () {
const data = TimePickerThemeData();
expect(identical(TimePickerThemeData.lerp(data, data, 0.5), data), true);
});
test('TimePickerThemeData has null fields by default', () {
const timePickerTheme = TimePickerThemeData();
expect(timePickerTheme.backgroundColor, null);
expect(timePickerTheme.cancelButtonStyle, null);
expect(timePickerTheme.confirmButtonStyle, null);
expect(timePickerTheme.dayPeriodBorderSide, null);
expect(timePickerTheme.dayPeriodColor, null);
expect(timePickerTheme.dayPeriodShape, null);
expect(timePickerTheme.dayPeriodTextColor, null);
expect(timePickerTheme.dayPeriodTextStyle, null);
expect(timePickerTheme.dialBackgroundColor, null);
expect(timePickerTheme.dialHandColor, null);
expect(timePickerTheme.dialTextColor, null);
expect(timePickerTheme.dialTextStyle, null);
expect(timePickerTheme.elevation, null);
expect(timePickerTheme.entryModeIconColor, null);
expect(timePickerTheme.helpTextStyle, null);
expect(timePickerTheme.hourMinuteColor, null);
expect(timePickerTheme.hourMinuteShape, null);
expect(timePickerTheme.hourMinuteTextColor, null);
expect(timePickerTheme.hourMinuteTextStyle, null);
expect(timePickerTheme.inputDecorationTheme, null);
expect(timePickerTheme.entryModeIconColor, null);
expect(timePickerTheme.padding, null);
expect(timePickerTheme.shape, null);
expect(timePickerTheme.timeSelectorSeparatorColor, null);
expect(timePickerTheme.timeSelectorSeparatorTextStyle, null);
});
testWidgets('Default TimePickerThemeData debugFillProperties', (WidgetTester tester) async {
final builder = DiagnosticPropertiesBuilder();
const TimePickerThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('TimePickerThemeData implements debugFillProperties', (WidgetTester tester) async {
final builder = DiagnosticPropertiesBuilder();
const TimePickerThemeData(
backgroundColor: Color(0xfffffff0),
cancelButtonStyle: ButtonStyle(
foregroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffff1)),
),
confirmButtonStyle: ButtonStyle(
foregroundColor: MaterialStatePropertyAll<Color>(Color(0xfffffff2)),
),
dayPeriodBorderSide: BorderSide(color: Color(0xfffffff3)),
dayPeriodColor: Color(0x00000000),
dayPeriodShape: RoundedRectangleBorder(side: BorderSide(color: Color(0xfffffff5))),
dayPeriodTextColor: Color(0xfffffff6),
dayPeriodTextStyle: TextStyle(color: Color(0xfffffff7)),
dialBackgroundColor: Color(0xfffffff8),
dialHandColor: Color(0xfffffff9),
dialTextColor: Color(0xfffffffa),
dialTextStyle: TextStyle(color: Color(0xfffffffb)),
elevation: 1.0,
entryModeIconColor: Color(0xfffffffc),
helpTextStyle: TextStyle(color: Color(0xfffffffd)),
hourMinuteColor: Color(0xfffffffe),
hourMinuteShape: RoundedRectangleBorder(side: BorderSide(color: Color(0xffffffff))),
hourMinuteTextColor: Color(0xfffffff0),
hourMinuteTextStyle: TextStyle(color: Color(0xfffffff1)),
inputDecorationTheme: InputDecorationTheme(labelStyle: TextStyle(color: Color(0xfffffff2))),
padding: EdgeInsets.all(1.0),
shape: RoundedRectangleBorder(side: BorderSide(color: Color(0xfffffff3))),
timeSelectorSeparatorColor: WidgetStatePropertyAll<Color>(Color(0xfffffff4)),
timeSelectorSeparatorTextStyle: WidgetStatePropertyAll<TextStyle>(
TextStyle(color: Color(0xfffffff5)),
),
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(
description,
equalsIgnoringHashCodes(<String>[
'backgroundColor: ${const Color(0xfffffff0)}',
'cancelButtonStyle: ButtonStyle#00000(foregroundColor: WidgetStatePropertyAll(${const Color(0xfffffff1)}))',
'confirmButtonStyle: ButtonStyle#00000(foregroundColor: WidgetStatePropertyAll(${const Color(0xfffffff2)}))',
'dayPeriodBorderSide: BorderSide(color: ${const Color(0xfffffff3)})',
'dayPeriodColor: ${const Color(0x00000000)}',
'dayPeriodShape: RoundedRectangleBorder(BorderSide(color: ${const Color(0xfffffff5)}), BorderRadius.zero)',
'dayPeriodTextColor: ${const Color(0xfffffff6)}',
'dayPeriodTextStyle: TextStyle(inherit: true, color: ${const Color(0xfffffff7)})',
'dialBackgroundColor: ${const Color(0xfffffff8)}',
'dialHandColor: ${const Color(0xfffffff9)}',
'dialTextColor: ${const Color(0xfffffffa)}',
'dialTextStyle: TextStyle(inherit: true, color: ${const Color(0xfffffffb)})',
'elevation: 1.0',
'entryModeIconColor: ${const Color(0xfffffffc)}',
'helpTextStyle: TextStyle(inherit: true, color: ${const Color(0xfffffffd)})',
'hourMinuteColor: ${const Color(0xfffffffe)}',
'hourMinuteShape: RoundedRectangleBorder(BorderSide(color: ${const Color(0xffffffff)}), BorderRadius.zero)',
'hourMinuteTextColor: ${const Color(0xfffffff0)}',
'hourMinuteTextStyle: TextStyle(inherit: true, color: ${const Color(0xfffffff1)})',
'inputDecorationTheme: InputDecorationThemeData#ff861(labelStyle: TextStyle(inherit: true, color: ${const Color(0xfffffff2)}))',
'padding: EdgeInsets.all(1.0)',
'shape: RoundedRectangleBorder(BorderSide(color: ${const Color(0xfffffff3)}), BorderRadius.zero)',
'timeSelectorSeparatorColor: WidgetStatePropertyAll(${const Color(0xfffffff4)})',
'timeSelectorSeparatorTextStyle: WidgetStatePropertyAll(TextStyle(inherit: true, color: ${const Color(0xfffffff5)}))',
]),
);
});
test(
'TimePickerThemeData.inputDecorationTheme accepts only InputDecorationTheme or InputDecorationThemeData instances',
() {
const decorationTheme = InputDecorationTheme();
var timePickerTheme = const TimePickerThemeData(inputDecorationTheme: decorationTheme);
expect(timePickerTheme.inputDecorationTheme, decorationTheme.data);
timePickerTheme = TimePickerThemeData(inputDecorationTheme: decorationTheme.data);
expect(timePickerTheme.inputDecorationTheme, decorationTheme.data);
// Wrong type throws.
expect(() {
TimePickerThemeData(inputDecorationTheme: Object());
}, throwsA(isA<AssertionError>()));
},
);
testWidgets('Material2 - Passing no TimePickerThemeData uses defaults', (
WidgetTester tester,
) async {
final defaultTheme = ThemeData(useMaterial3: false);
await tester.pumpWidget(_TimePickerLauncher(themeData: defaultTheme));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Material dialogMaterial = _dialogMaterial(tester);
expect(dialogMaterial.color, defaultTheme.colorScheme.surface);
expect(
dialogMaterial.shape,
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
);
final RenderBox dial = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
expect(
dial,
paints
..circle(
color: defaultTheme.colorScheme.onSurface.withOpacity(0.08),
) // Dial background color.
..circle(color: Color(defaultTheme.colorScheme.primary.value)),
);
final RenderParagraph hourText = _textRenderParagraph(tester, '7');
expect(
hourText.text.style,
Typography.material2014().englishLike.displayMedium!
.merge(Typography.material2014().black.displayMedium)
.copyWith(color: defaultTheme.colorScheme.primary),
);
final RenderParagraph minuteText = _textRenderParagraph(tester, '15');
expect(
minuteText.text.style,
Typography.material2014().englishLike.displayMedium!
.merge(Typography.material2014().black.displayMedium)
.copyWith(color: defaultTheme.colorScheme.onSurface),
);
final RenderParagraph amText = _textRenderParagraph(tester, 'AM');
expect(
amText.text.style,
Typography.material2014().englishLike.titleMedium!
.merge(Typography.material2014().black.titleMedium)
.copyWith(color: defaultTheme.colorScheme.primary),
);
final RenderParagraph pmText = _textRenderParagraph(tester, 'PM');
expect(
pmText.text.style,
Typography.material2014().englishLike.titleMedium!
.merge(Typography.material2014().black.titleMedium)
.copyWith(color: defaultTheme.colorScheme.onSurface.withOpacity(0.6)),
);
final RenderParagraph helperText = _textRenderParagraph(tester, 'SELECT TIME');
expect(
helperText.text.style,
Typography.material2014().englishLike.labelSmall!.merge(
Typography.material2014().black.labelSmall,
),
);
final CustomPaint dialPaint = tester.widget(findDialPaint);
final dynamic dialPainter = dialPaint.painter;
// ignore: avoid_dynamic_calls
final primaryLabels = dialPainter.primaryLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
primaryLabels.first.painter.text.style,
Typography.material2014().englishLike.bodyLarge!
.merge(Typography.material2014().black.bodyLarge)
.copyWith(color: defaultTheme.colorScheme.onSurface),
);
// ignore: avoid_dynamic_calls
final selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
selectedLabels.first.painter.text.style,
Typography.material2014().englishLike.bodyLarge!
.merge(Typography.material2014().white.bodyLarge)
.copyWith(color: defaultTheme.colorScheme.onPrimary),
);
final Material hourMaterial = _textMaterial(tester, '7');
expect(hourMaterial.color, defaultTheme.colorScheme.primary.withOpacity(0.12));
expect(
hourMaterial.shape,
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
);
final Material minuteMaterial = _textMaterial(tester, '15');
expect(minuteMaterial.color, defaultTheme.colorScheme.onSurface.withOpacity(0.12));
expect(
minuteMaterial.shape,
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
);
final Material amMaterial = _textMaterial(tester, 'AM');
expect(amMaterial.color, defaultTheme.colorScheme.primary.withOpacity(0.12));
final Material pmMaterial = _textMaterial(tester, 'PM');
expect(pmMaterial.color, Colors.transparent);
final Color expectedBorderColor = Color.alphaBlend(
defaultTheme.colorScheme.onSurface.withOpacity(0.38),
defaultTheme.colorScheme.surface,
);
final expectedAmShape = RoundedRectangleBorder(
side: BorderSide(color: expectedBorderColor),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4.0),
bottomLeft: Radius.circular(4.0),
),
);
expect(amMaterial.shape, expectedAmShape);
final expectedPmShape = RoundedRectangleBorder(
side: BorderSide(color: expectedBorderColor),
borderRadius: const BorderRadius.only(
topRight: Radius.circular(4.0),
bottomRight: Radius.circular(4.0),
),
);
expect(pmMaterial.shape, expectedPmShape);
expect(
find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl'),
matching: find.byType(Container),
),
findsNothing,
);
final IconButton entryModeIconButton = _entryModeIconButton(tester);
expect(entryModeIconButton.color, defaultTheme.colorScheme.onSurface.withOpacity(0.6));
final ButtonStyle cancelButtonStyle = _actionButtonStyle(tester, 'CANCEL');
expect(
cancelButtonStyle.toString(),
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
final ButtonStyle confirmButtonStyle = _actionButtonStyle(tester, 'OK');
expect(
confirmButtonStyle.toString(),
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
});
testWidgets('Material3 - Passing no TimePickerThemeData uses defaults', (
WidgetTester tester,
) async {
final defaultTheme = ThemeData();
await tester.pumpWidget(_TimePickerLauncher(themeData: defaultTheme));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Material dialogMaterial = _dialogMaterial(tester);
expect(dialogMaterial.color, defaultTheme.colorScheme.surfaceContainerHigh);
expect(
dialogMaterial.shape,
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0))),
);
final RenderBox dial = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
expect(
dial,
paints
..circle(color: defaultTheme.colorScheme.surfaceContainerHighest) // Dial background color.
..circle(color: Color(defaultTheme.colorScheme.primary.value)), // Dial hand color.
);
final RenderParagraph hourText = _textRenderParagraph(tester, '7');
expect(
hourText.text.style,
Typography.material2021().englishLike.displayLarge!
.merge(Typography.material2021().black.displayLarge)
.copyWith(
color: defaultTheme.colorScheme.onPrimaryContainer,
decorationColor: defaultTheme.colorScheme.onSurface,
),
);
final RenderParagraph minuteText = _textRenderParagraph(tester, '15');
expect(
minuteText.text.style,
Typography.material2021().englishLike.displayLarge!
.merge(Typography.material2021().black.displayLarge)
.copyWith(
color: defaultTheme.colorScheme.onSurface,
decorationColor: defaultTheme.colorScheme.onSurface,
),
);
final RenderParagraph amText = _textRenderParagraph(tester, 'AM');
expect(
amText.text.style,
Typography.material2021().englishLike.titleMedium!
.merge(Typography.material2021().black.titleMedium)
.copyWith(
color: defaultTheme.colorScheme.onTertiaryContainer,
decorationColor: defaultTheme.colorScheme.onSurface,
),
);
final RenderParagraph pmText = _textRenderParagraph(tester, 'PM');
expect(
pmText.text.style,
Typography.material2021().englishLike.titleMedium!
.merge(Typography.material2021().black.titleMedium)
.copyWith(
color: defaultTheme.colorScheme.onSurfaceVariant,
decorationColor: defaultTheme.colorScheme.onSurface,
),
);
final RenderParagraph helperText = _textRenderParagraph(tester, 'Select time');
expect(
helperText.text.style,
Typography.material2021().englishLike.bodyMedium!
.merge(Typography.material2021().black.bodyMedium)
.copyWith(
color: defaultTheme.colorScheme.onSurface,
decorationColor: defaultTheme.colorScheme.onSurface,
),
);
final CustomPaint dialPaint = tester.widget(findDialPaint);
final dynamic dialPainter = dialPaint.painter;
// ignore: avoid_dynamic_calls
final primaryLabels = dialPainter.primaryLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
primaryLabels.first.painter.text.style,
Typography.material2021().englishLike.bodyLarge!
.merge(Typography.material2021().black.bodyLarge)
.copyWith(
color: defaultTheme.colorScheme.onSurface,
decorationColor: defaultTheme.colorScheme.onSurface,
),
);
// ignore: avoid_dynamic_calls
final selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
selectedLabels.first.painter.text.style,
Typography.material2021().englishLike.bodyLarge!
.merge(Typography.material2021().black.bodyLarge)
.copyWith(
color: defaultTheme.colorScheme.onPrimary,
decorationColor: defaultTheme.colorScheme.onSurface,
),
);
final Material hourMaterial = _textMaterial(tester, '7');
expect(hourMaterial.color, defaultTheme.colorScheme.primaryContainer);
expect(
hourMaterial.shape,
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
);
final Material minuteMaterial = _textMaterial(tester, '15');
expect(minuteMaterial.color, defaultTheme.colorScheme.surfaceContainerHighest);
expect(
minuteMaterial.shape,
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
);
final Material amMaterial = _textMaterial(tester, 'AM');
expect(amMaterial.color, defaultTheme.colorScheme.tertiaryContainer);
final Material pmMaterial = _textMaterial(tester, 'PM');
expect(pmMaterial.color, Colors.transparent);
final expectedAmShape = RoundedRectangleBorder(
side: BorderSide(color: defaultTheme.colorScheme.outline),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8.0),
bottomLeft: Radius.circular(8.0),
),
);
expect(amMaterial.shape, expectedAmShape);
final expectedPmShape = RoundedRectangleBorder(
side: BorderSide(color: defaultTheme.colorScheme.outline),
borderRadius: const BorderRadius.only(
topRight: Radius.circular(8.0),
bottomRight: Radius.circular(8.0),
),
);
expect(pmMaterial.shape, expectedPmShape);
expect(
find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl'),
matching: find.byType(Container),
),
findsNothing,
);
final IconButton entryModeIconButton = _entryModeIconButton(tester);
expect(entryModeIconButton.color, null);
final ButtonStyle cancelButtonStyle = _actionButtonStyle(tester, 'Cancel');
expect(
cancelButtonStyle.toString(),
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
final ButtonStyle confirmButtonStyle = _actionButtonStyle(tester, 'OK');
expect(
confirmButtonStyle.toString(),
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
});
testWidgets('Material2 - Passing no TimePickerThemeData uses defaults - input mode', (
WidgetTester tester,
) async {
final defaultTheme = ThemeData(useMaterial3: false);
await tester.pumpWidget(
_TimePickerLauncher(themeData: defaultTheme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final InputDecoration hourDecoration = _textField(tester, '7').decoration!;
expect(hourDecoration.filled, true);
expect(
hourDecoration.fillColor,
WidgetStateColor.resolveWith(
(Set<WidgetState> states) => defaultTheme.colorScheme.onSurface.withOpacity(0.12),
),
);
expect(
hourDecoration.enabledBorder,
const OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent)),
);
expect(
hourDecoration.errorBorder,
OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2)),
);
expect(
hourDecoration.focusedBorder,
OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.primary, width: 2)),
);
expect(
hourDecoration.focusedErrorBorder,
OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2)),
);
expect(
hourDecoration.hintStyle,
Typography.material2014().englishLike.displayMedium!.merge(
defaultTheme.textTheme.displayMedium!.copyWith(
color: defaultTheme.colorScheme.onSurface.withOpacity(0.36),
),
),
);
final ButtonStyle cancelButtonStyle = _actionButtonStyle(tester, 'CANCEL');
expect(
cancelButtonStyle.toString(),
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
final ButtonStyle confirmButtonStyle = _actionButtonStyle(tester, 'OK');
expect(
confirmButtonStyle.toString(),
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
});
testWidgets('Material3 - Passing no TimePickerThemeData uses defaults - input mode', (
WidgetTester tester,
) async {
final defaultTheme = ThemeData();
await tester.pumpWidget(
_TimePickerLauncher(themeData: defaultTheme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final TextStyle hourTextStyle = _textField(tester, '7').style!;
expect(
hourTextStyle,
Typography.material2021().englishLike.displayMedium!
.merge(Typography.material2021().black.displayMedium)
.copyWith(
color: defaultTheme.colorScheme.onSurface,
decorationColor: defaultTheme.colorScheme.onSurface,
),
);
final TextStyle minuteTextStyle = _textField(tester, '15').style!;
expect(
minuteTextStyle,
Typography.material2021().englishLike.displayMedium!
.merge(Typography.material2021().black.displayMedium)
.copyWith(
color: defaultTheme.colorScheme.onSurface,
decorationColor: defaultTheme.colorScheme.onSurface,
),
);
final InputDecoration hourDecoration = _textField(tester, '7').decoration!;
expect(hourDecoration.filled, true);
expect(hourDecoration.fillColor, defaultTheme.colorScheme.surfaceContainerHighest);
expect(
hourDecoration.enabledBorder,
const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
borderSide: BorderSide(color: Colors.transparent),
),
);
expect(
hourDecoration.errorBorder,
OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2.0),
),
);
expect(
hourDecoration.focusedBorder,
OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
borderSide: BorderSide(color: defaultTheme.colorScheme.primary, width: 2.0),
),
);
expect(
hourDecoration.focusedErrorBorder,
OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2.0),
),
);
expect(
hourDecoration.hintStyle,
TextStyle(color: defaultTheme.colorScheme.onSurface.withOpacity(0.36)),
);
final ButtonStyle cancelButtonStyle = _actionButtonStyle(tester, 'Cancel');
expect(
cancelButtonStyle.toString(),
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
final ButtonStyle confirmButtonStyle = _actionButtonStyle(tester, 'OK');
expect(
confirmButtonStyle.toString(),
equalsIgnoringHashCodes(TextButton.styleFrom().toString()),
);
});
testWidgets('Material2 - Time picker uses values from TimePickerThemeData', (
WidgetTester tester,
) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme();
final theme = ThemeData(timePickerTheme: timePickerTheme, useMaterial3: false);
await tester.pumpWidget(_TimePickerLauncher(themeData: theme));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Material dialogMaterial = _dialogMaterial(tester);
expect(dialogMaterial.color, timePickerTheme.backgroundColor);
expect(dialogMaterial.shape, timePickerTheme.shape);
final RenderBox dial = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
expect(
dial,
paints
..circle(color: Color(timePickerTheme.dialBackgroundColor!.value)) // Dial background color.
..circle(color: Color(timePickerTheme.dialHandColor!.value)), // Dial hand color.
);
final RenderParagraph hourText = _textRenderParagraph(tester, '7');
expect(
hourText.text.style,
Typography.material2014().englishLike.bodyMedium!
.merge(Typography.material2014().black.bodyMedium)
.merge(timePickerTheme.hourMinuteTextStyle)
.copyWith(color: _selectedColor),
);
final RenderParagraph minuteText = _textRenderParagraph(tester, '15');
expect(
minuteText.text.style,
Typography.material2014().englishLike.bodyMedium!
.merge(Typography.material2014().black.bodyMedium)
.merge(timePickerTheme.hourMinuteTextStyle)
.copyWith(color: _unselectedColor),
);
final RenderParagraph amText = _textRenderParagraph(tester, 'AM');
expect(
amText.text.style,
Typography.material2014().englishLike.titleMedium!
.merge(Typography.material2014().black.titleMedium)
.merge(timePickerTheme.dayPeriodTextStyle)
.copyWith(color: _selectedColor),
);
final RenderParagraph pmText = _textRenderParagraph(tester, 'PM');
expect(
pmText.text.style,
Typography.material2014().englishLike.titleMedium!
.merge(Typography.material2014().black.titleMedium)
.merge(timePickerTheme.dayPeriodTextStyle)
.copyWith(color: _unselectedColor),
);
final RenderParagraph helperText = _textRenderParagraph(tester, 'SELECT TIME');
expect(
helperText.text.style,
Typography.material2014().englishLike.bodyMedium!
.merge(Typography.material2014().black.bodyMedium)
.merge(timePickerTheme.helpTextStyle),
);
final CustomPaint dialPaint = tester.widget(findDialPaint);
final dynamic dialPainter = dialPaint.painter;
// ignore: avoid_dynamic_calls
final primaryLabels = dialPainter.primaryLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
primaryLabels.first.painter.text.style,
Typography.material2014().englishLike.bodyLarge!
.merge(Typography.material2014().black.bodyLarge)
.copyWith(color: _unselectedColor),
);
// ignore: avoid_dynamic_calls
final selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
selectedLabels.first.painter.text.style,
Typography.material2014().englishLike.bodyLarge!
.merge(Typography.material2014().white.bodyLarge)
.copyWith(color: _selectedColor),
);
final Material hourMaterial = _textMaterial(tester, '7');
expect(hourMaterial.color, _selectedColor);
expect(hourMaterial.shape, timePickerTheme.hourMinuteShape);
final Material minuteMaterial = _textMaterial(tester, '15');
expect(minuteMaterial.color, _unselectedColor);
expect(minuteMaterial.shape, timePickerTheme.hourMinuteShape);
final Material amMaterial = _textMaterial(tester, 'AM');
expect(amMaterial.color, _selectedColor);
final Material pmMaterial = _textMaterial(tester, 'PM');
expect(pmMaterial.color, _unselectedColor);
final dayPeriodShape = timePickerTheme.dayPeriodShape! as RoundedRectangleBorder;
final borderRadius = dayPeriodShape.borderRadius as BorderRadius;
final RoundedRectangleBorder expectedAmShape = dayPeriodShape.copyWith(
side: timePickerTheme.dayPeriodBorderSide,
borderRadius: BorderRadius.only(
topLeft: borderRadius.topLeft,
bottomLeft: borderRadius.topRight,
),
);
expect(amMaterial.shape, expectedAmShape);
final RoundedRectangleBorder expectedPmShape = dayPeriodShape.copyWith(
side: timePickerTheme.dayPeriodBorderSide,
borderRadius: BorderRadius.only(
topRight: borderRadius.topLeft,
bottomRight: borderRadius.topRight,
),
);
expect(pmMaterial.shape, expectedPmShape);
expect(
find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl'),
matching: find.byType(Container),
),
findsNothing,
);
final IconButton entryModeIconButton = _entryModeIconButton(tester);
expect(entryModeIconButton.color, timePickerTheme.entryModeIconColor);
final ButtonStyle cancelButtonStyle = _actionButtonStyle(tester, 'CANCEL');
expect(
cancelButtonStyle.toString(),
equalsIgnoringHashCodes(timePickerTheme.cancelButtonStyle.toString()),
);
final ButtonStyle confirmButtonStyle = _actionButtonStyle(tester, 'OK');
expect(
confirmButtonStyle.toString(),
equalsIgnoringHashCodes(timePickerTheme.confirmButtonStyle.toString()),
);
});
testWidgets('Material3 - Time picker uses values from TimePickerThemeData', (
WidgetTester tester,
) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme();
final theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(_TimePickerLauncher(themeData: theme));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Material dialogMaterial = _dialogMaterial(tester);
expect(dialogMaterial.color, timePickerTheme.backgroundColor);
expect(dialogMaterial.shape, timePickerTheme.shape);
final RenderBox dial = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
expect(
dial,
paints
..circle(color: Color(timePickerTheme.dialBackgroundColor!.value)) // Dial background color.
..circle(color: Color(timePickerTheme.dialHandColor!.value)), // Dial hand color.
);
final RenderParagraph hourText = _textRenderParagraph(tester, '7');
expect(
hourText.text.style,
Typography.material2021().englishLike.bodyMedium!
.merge(Typography.material2021().black.bodyMedium)
.merge(timePickerTheme.hourMinuteTextStyle)
.copyWith(color: _selectedColor, decorationColor: const Color(0xff1d1b20)),
);
final RenderParagraph minuteText = _textRenderParagraph(tester, '15');
expect(
minuteText.text.style,
Typography.material2021().englishLike.bodyMedium!
.merge(Typography.material2021().black.bodyMedium)
.merge(timePickerTheme.hourMinuteTextStyle)
.copyWith(color: _unselectedColor, decorationColor: const Color(0xff1d1b20)),
);
final RenderParagraph amText = _textRenderParagraph(tester, 'AM');
expect(
amText.text.style,
Typography.material2021().englishLike.bodyMedium!
.merge(Typography.material2021().black.bodyMedium)
.merge(timePickerTheme.hourMinuteTextStyle)
.copyWith(color: _selectedColor, decorationColor: const Color(0xff1d1b20)),
);
final RenderParagraph pmText = _textRenderParagraph(tester, 'PM');
expect(
pmText.text.style,
Typography.material2021().englishLike.bodyMedium!
.merge(Typography.material2021().black.bodyMedium)
.merge(timePickerTheme.hourMinuteTextStyle)
.copyWith(color: _unselectedColor, decorationColor: const Color(0xff1d1b20)),
);
final RenderParagraph helperText = _textRenderParagraph(tester, 'Select time');
expect(
helperText.text.style,
Typography.material2021().englishLike.bodyMedium!
.merge(Typography.material2021().black.bodyMedium)
.merge(timePickerTheme.helpTextStyle)
.copyWith(
color: theme.colorScheme.onSurface,
decorationColor: theme.colorScheme.onSurface,
),
);
final CustomPaint dialPaint = tester.widget(findDialPaint);
final dynamic dialPainter = dialPaint.painter;
// ignore: avoid_dynamic_calls
final primaryLabels = dialPainter.primaryLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
primaryLabels.first.painter.text.style,
Typography.material2021().englishLike.bodyLarge!
.merge(Typography.material2021().black.bodyLarge)
.copyWith(color: _unselectedColor, decorationColor: theme.colorScheme.onSurface),
);
// ignore: avoid_dynamic_calls
final selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
selectedLabels.first.painter.text.style,
Typography.material2021().englishLike.bodyLarge!
.merge(Typography.material2021().black.bodyLarge)
.copyWith(color: _selectedColor, decorationColor: theme.colorScheme.onSurface),
);
final Material hourMaterial = _textMaterial(tester, '7');
expect(hourMaterial.color, _selectedColor);
expect(hourMaterial.shape, timePickerTheme.hourMinuteShape);
final Material minuteMaterial = _textMaterial(tester, '15');
expect(minuteMaterial.color, _unselectedColor);
expect(minuteMaterial.shape, timePickerTheme.hourMinuteShape);
final Material amMaterial = _textMaterial(tester, 'AM');
expect(amMaterial.color, _selectedColor);
final Material pmMaterial = _textMaterial(tester, 'PM');
expect(pmMaterial.color, _unselectedColor);
final dayPeriodShape = timePickerTheme.dayPeriodShape! as RoundedRectangleBorder;
final borderRadius = dayPeriodShape.borderRadius as BorderRadius;
final RoundedRectangleBorder expectedAmShape = dayPeriodShape.copyWith(
side: timePickerTheme.dayPeriodBorderSide,
borderRadius: BorderRadius.only(
topLeft: borderRadius.topLeft,
bottomLeft: borderRadius.topRight,
),
);
expect(amMaterial.shape, expectedAmShape);
final RoundedRectangleBorder expectedPmShape = dayPeriodShape.copyWith(
side: timePickerTheme.dayPeriodBorderSide,
borderRadius: BorderRadius.only(
topRight: borderRadius.topLeft,
bottomRight: borderRadius.topRight,
),
);
expect(pmMaterial.shape, expectedPmShape);
expect(
find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl'),
matching: find.byType(Container),
),
findsNothing,
);
final IconButton entryModeIconButton = _entryModeIconButton(tester);
expect(entryModeIconButton.color, null);
final ButtonStyle cancelButtonStyle = _actionButtonStyle(tester, 'Cancel');
expect(
cancelButtonStyle.toString(),
equalsIgnoringHashCodes(timePickerTheme.cancelButtonStyle.toString()),
);
final ButtonStyle confirmButtonStyle = _actionButtonStyle(tester, 'OK');
expect(
confirmButtonStyle.toString(),
equalsIgnoringHashCodes(timePickerTheme.confirmButtonStyle.toString()),
);
});
testWidgets(
'Time picker uses values from TimePickerThemeData when TimePickerThemeData.inputDecorationTheme is provided - input mode',
(WidgetTester tester) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme(includeInputDecoration: true);
final theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(
_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final InputDecoration hourDecoration = _textField(tester, '7').decoration!;
expect(hourDecoration.filled, timePickerTheme.inputDecorationTheme!.filled);
expect(hourDecoration.fillColor, timePickerTheme.inputDecorationTheme!.fillColor);
expect(hourDecoration.enabledBorder, timePickerTheme.inputDecorationTheme!.enabledBorder);
expect(hourDecoration.errorBorder, timePickerTheme.inputDecorationTheme!.errorBorder);
expect(hourDecoration.focusedBorder, timePickerTheme.inputDecorationTheme!.focusedBorder);
expect(
hourDecoration.focusedErrorBorder,
timePickerTheme.inputDecorationTheme!.focusedErrorBorder,
);
expect(hourDecoration.hintStyle, timePickerTheme.inputDecorationTheme!.hintStyle);
},
);
testWidgets(
'Time picker uses values from TimePickerThemeData when TimePickerThemeData.inputDecorationTheme is not provided - input mode',
(WidgetTester tester) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme();
final theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(
_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final InputDecoration hourDecoration = _textField(tester, '7').decoration!;
expect(hourDecoration.fillColor?.value, timePickerTheme.hourMinuteColor?.value);
},
);
testWidgets('Time picker dayPeriodColor does the right thing with non-WidgetStateColor', (
WidgetTester tester,
) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme().copyWith(
dayPeriodColor: Colors.red,
);
final theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(
_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Material amMaterial = _textMaterial(tester, 'AM');
expect(amMaterial.color, Colors.red);
final Material pmMaterial = _textMaterial(tester, 'PM');
expect(pmMaterial.color, Colors.transparent);
});
testWidgets('Time picker dayPeriodColor does the right thing with WidgetStateColor', (
WidgetTester tester,
) async {
final testColor = WidgetStateColor.resolveWith((Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return Colors.green;
}
return Colors.blue;
});
final TimePickerThemeData timePickerTheme = _timePickerTheme().copyWith(
dayPeriodColor: testColor,
);
final theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(
_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Material amMaterial = _textMaterial(tester, 'AM');
expect(amMaterial.color, Colors.green);
final Material pmMaterial = _textMaterial(tester, 'PM');
expect(pmMaterial.color, Colors.blue);
});
testWidgets('Time selector separator color uses the timeSelectorSeparatorColor value', (
WidgetTester tester,
) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme().copyWith(
timeSelectorSeparatorColor: const MaterialStatePropertyAll<Color>(Color(0xff00ff00)),
);
final theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(
_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final RenderParagraph paragraph = tester.renderObject(find.text(':'));
expect(paragraph.text.style!.color, const Color(0xff00ff00));
});
testWidgets('Time selector separator text style uses the timeSelectorSeparatorTextStyle value', (
WidgetTester tester,
) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme().copyWith(
timeSelectorSeparatorTextStyle: const MaterialStatePropertyAll<TextStyle>(
TextStyle(fontSize: 35.0, fontStyle: FontStyle.italic),
),
);
final theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(
_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final RenderParagraph paragraph = tester.renderObject(find.text(':'));
expect(paragraph.text.style!.fontSize, 35.0);
expect(paragraph.text.style!.fontStyle, FontStyle.italic);
});
// This is a regression test for https://github.com/flutter/flutter/issues/153549.
testWidgets('Time picker hour minute does not resize on error', (WidgetTester tester) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme(includeInputDecoration: true);
final theme = ThemeData(timePickerTheme: timePickerTheme);
await tester.pumpWidget(
_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0));
// Enter invalid hour.
await tester.enterText(find.byType(TextField).first, 'AB');
await tester.tap(find.text('OK'));
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0));
});
// This is a regression test for https://github.com/flutter/flutter/issues/153549.
testWidgets('Material2 - Time picker hour minute does not resize on error', (
WidgetTester tester,
) async {
final TimePickerThemeData timePickerTheme = _timePickerTheme(includeInputDecoration: true);
final theme = ThemeData(timePickerTheme: timePickerTheme, useMaterial3: false);
await tester.pumpWidget(
_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
// Enter invalid hour.
await tester.enterText(find.byType(TextField).first, 'AB');
await tester.tap(find.text('OK'));
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
});
}
final Color _selectedColor = Colors.green[100]!;
final Color _unselectedColor = Colors.green[200]!;
TimePickerThemeData _timePickerTheme({bool includeInputDecoration = false}) {
Color getColor(Set<WidgetState> states) {
return states.contains(WidgetState.selected) ? _selectedColor : _unselectedColor;
}
final materialStateColor = WidgetStateColor.resolveWith(getColor);
return TimePickerThemeData(
backgroundColor: Colors.orange,
cancelButtonStyle: TextButton.styleFrom(foregroundColor: Colors.red),
confirmButtonStyle: TextButton.styleFrom(foregroundColor: Colors.green),
hourMinuteTextColor: materialStateColor,
hourMinuteColor: materialStateColor,
dayPeriodTextColor: materialStateColor,
dayPeriodColor: materialStateColor,
dialHandColor: Colors.brown,
dialBackgroundColor: Colors.pinkAccent,
dialTextColor: materialStateColor,
entryModeIconColor: Colors.red,
hourMinuteTextStyle: const TextStyle(fontSize: 8.0),
dayPeriodTextStyle: const TextStyle(fontSize: 8.0),
helpTextStyle: const TextStyle(fontSize: 8.0),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))),
hourMinuteShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
dayPeriodShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
dayPeriodBorderSide: const BorderSide(color: Colors.blueAccent),
inputDecorationTheme: includeInputDecoration
? const InputDecorationTheme(
filled: true,
fillColor: Colors.purple,
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue)),
errorBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.green)),
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.yellow)),
focusedErrorBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.red)),
hintStyle: TextStyle(fontSize: 8),
)
: null,
);
}
class _TimePickerLauncher extends StatelessWidget {
const _TimePickerLauncher({this.themeData, this.entryMode = TimePickerEntryMode.dial});
final ThemeData? themeData;
final TimePickerEntryMode entryMode;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: themeData,
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
child: const Text('X'),
onPressed: () async {
await showTimePicker(
context: context,
initialEntryMode: entryMode,
initialTime: const TimeOfDay(hour: 7, minute: 15),
);
},
);
},
),
),
),
);
}
}
Material _dialogMaterial(WidgetTester tester) {
return tester.widget<Material>(
find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first,
);
}
Material _textMaterial(WidgetTester tester, String text) {
return tester.widget<Material>(
find.ancestor(of: find.text(text), matching: find.byType(Material)).first,
);
}
TextField _textField(WidgetTester tester, String text) {
return tester.widget<TextField>(
find.ancestor(of: find.text(text), matching: find.byType(TextField)).first,
);
}
IconButton _entryModeIconButton(WidgetTester tester) {
return tester.widget<IconButton>(
find.descendant(of: find.byType(Dialog), matching: find.byType(IconButton)).first,
);
}
RenderParagraph _textRenderParagraph(WidgetTester tester, String text) {
return tester.element<StatelessElement>(find.text(text).first).renderObject! as RenderParagraph;
}
final Finder findDialPaint = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
matching: find.byWidgetPredicate((Widget w) => w is CustomPaint),
);
ButtonStyle _actionButtonStyle(WidgetTester tester, String text) {
return tester.widget<TextButton>(find.widgetWithText(TextButton, text)).style!;
}
Finder findBorderPainter() {
return find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_BorderContainer'),
matching: find.byWidgetPredicate((Widget w) => w is CustomPaint),
);
}