mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
While I was looking through `time.dart`, I noticed that `hourFormat` was still using the old switch-case style with a lot of boilerplate. Since we are moving towards Dart 3 patterns, I thought it'd be a good idea to swap this out for a switch expression. It's much cleaner now and handles the `TimeOfDayFormat` mapping in just a few lines. I also realized there weren't any direct tests for this specific function (it was mostly covered by larger integration tests), so I added a small test group in `time_test.dart` to make sure every enum value maps to the right `HourFormat` correctly. This is a trivial internal refactor and does not fix a specific bug report, so no issue is linked. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. (I will sign it once the bot prompts me) - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
332 lines
11 KiB
Dart
332 lines
11 KiB
Dart
// 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_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
group('hourFormat', () {
|
|
test('returns HourFormat.h for 12-hour formats', () {
|
|
const formats = <TimeOfDayFormat>[
|
|
TimeOfDayFormat.h_colon_mm_space_a,
|
|
TimeOfDayFormat.a_space_h_colon_mm,
|
|
];
|
|
for (final format in formats) {
|
|
expect(hourFormat(of: format), HourFormat.h, reason: 'for $format');
|
|
}
|
|
});
|
|
|
|
test('returns HourFormat.H for non-padded 24-hour format', () {
|
|
expect(hourFormat(of: TimeOfDayFormat.H_colon_mm), HourFormat.H);
|
|
});
|
|
|
|
test('returns HourFormat.HH for zero-padded 24-hour formats', () {
|
|
const formats = <TimeOfDayFormat>[
|
|
TimeOfDayFormat.HH_dot_mm,
|
|
TimeOfDayFormat.HH_colon_mm,
|
|
TimeOfDayFormat.frenchCanadian,
|
|
];
|
|
for (final format in formats) {
|
|
expect(hourFormat(of: format), HourFormat.HH, reason: 'for $format');
|
|
}
|
|
});
|
|
});
|
|
|
|
group('TimeOfDay.format', () {
|
|
testWidgets('respects alwaysUse24HourFormat option', (WidgetTester tester) async {
|
|
Future<String> pumpTest(bool alwaysUse24HourFormat) async {
|
|
late String formattedValue;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: MediaQuery(
|
|
data: MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat),
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
formattedValue = const TimeOfDay(hour: 7, minute: 0).format(context);
|
|
return Container();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
return formattedValue;
|
|
}
|
|
|
|
expect(await pumpTest(false), '7:00 AM');
|
|
expect(await pumpTest(true), '07:00');
|
|
});
|
|
});
|
|
|
|
testWidgets('hourOfPeriod returns correct value', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/59158.
|
|
expect(const TimeOfDay(minute: 0, hour: 0).hourOfPeriod, 12);
|
|
expect(const TimeOfDay(minute: 0, hour: 1).hourOfPeriod, 1);
|
|
expect(const TimeOfDay(minute: 0, hour: 2).hourOfPeriod, 2);
|
|
expect(const TimeOfDay(minute: 0, hour: 3).hourOfPeriod, 3);
|
|
expect(const TimeOfDay(minute: 0, hour: 4).hourOfPeriod, 4);
|
|
expect(const TimeOfDay(minute: 0, hour: 5).hourOfPeriod, 5);
|
|
expect(const TimeOfDay(minute: 0, hour: 6).hourOfPeriod, 6);
|
|
expect(const TimeOfDay(minute: 0, hour: 7).hourOfPeriod, 7);
|
|
expect(const TimeOfDay(minute: 0, hour: 8).hourOfPeriod, 8);
|
|
expect(const TimeOfDay(minute: 0, hour: 9).hourOfPeriod, 9);
|
|
expect(const TimeOfDay(minute: 0, hour: 10).hourOfPeriod, 10);
|
|
expect(const TimeOfDay(minute: 0, hour: 11).hourOfPeriod, 11);
|
|
expect(const TimeOfDay(minute: 0, hour: 12).hourOfPeriod, 12);
|
|
expect(const TimeOfDay(minute: 0, hour: 13).hourOfPeriod, 1);
|
|
expect(const TimeOfDay(minute: 0, hour: 14).hourOfPeriod, 2);
|
|
expect(const TimeOfDay(minute: 0, hour: 15).hourOfPeriod, 3);
|
|
expect(const TimeOfDay(minute: 0, hour: 16).hourOfPeriod, 4);
|
|
expect(const TimeOfDay(minute: 0, hour: 17).hourOfPeriod, 5);
|
|
expect(const TimeOfDay(minute: 0, hour: 18).hourOfPeriod, 6);
|
|
expect(const TimeOfDay(minute: 0, hour: 19).hourOfPeriod, 7);
|
|
expect(const TimeOfDay(minute: 0, hour: 20).hourOfPeriod, 8);
|
|
expect(const TimeOfDay(minute: 0, hour: 21).hourOfPeriod, 9);
|
|
expect(const TimeOfDay(minute: 0, hour: 22).hourOfPeriod, 10);
|
|
expect(const TimeOfDay(minute: 0, hour: 23).hourOfPeriod, 11);
|
|
});
|
|
|
|
group('RestorableTimeOfDay tests', () {
|
|
testWidgets('value is not accessible when not registered', (WidgetTester tester) async {
|
|
final property = RestorableTimeOfDay(const TimeOfDay(hour: 20, minute: 4));
|
|
addTearDown(property.dispose);
|
|
expect(() => property.value, throwsAssertionError);
|
|
});
|
|
|
|
testWidgets('work when not in restoration scope', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const _RestorableWidget());
|
|
|
|
final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));
|
|
|
|
// Initialized to default values.
|
|
expect(state.timeOfDay.value, const TimeOfDay(hour: 10, minute: 5));
|
|
|
|
// Modify values.
|
|
state.setProperties(() {
|
|
state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
|
|
});
|
|
await tester.pump();
|
|
|
|
expect(state.timeOfDay.value, const TimeOfDay(hour: 2, minute: 2));
|
|
});
|
|
|
|
testWidgets('restart and restore', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const RootRestorationScope(restorationId: 'root-child', child: _RestorableWidget()),
|
|
);
|
|
|
|
_RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));
|
|
|
|
// Initialized to default values.
|
|
expect(state.timeOfDay.value, const TimeOfDay(hour: 10, minute: 5));
|
|
|
|
// Modify values.
|
|
state.setProperties(() {
|
|
state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
|
|
});
|
|
await tester.pump();
|
|
|
|
expect(state.timeOfDay.value, const TimeOfDay(hour: 2, minute: 2));
|
|
|
|
// Restores to previous values.
|
|
await tester.restartAndRestore();
|
|
final oldState = state;
|
|
state = tester.state(find.byType(_RestorableWidget));
|
|
expect(state, isNot(same(oldState)));
|
|
|
|
expect(state.timeOfDay.value, const TimeOfDay(hour: 2, minute: 2));
|
|
});
|
|
|
|
testWidgets('restore to older state', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const RootRestorationScope(restorationId: 'root-child', child: _RestorableWidget()),
|
|
);
|
|
|
|
final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));
|
|
|
|
// Modify values.
|
|
state.setProperties(() {
|
|
state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
|
|
});
|
|
await tester.pump();
|
|
|
|
final TestRestorationData restorationData = await tester.getRestorationData();
|
|
|
|
// Modify values.
|
|
state.setProperties(() {
|
|
state.timeOfDay.value = const TimeOfDay(hour: 4, minute: 4);
|
|
});
|
|
await tester.pump();
|
|
|
|
// Restore to previous.
|
|
await tester.restoreFrom(restorationData);
|
|
expect(state.timeOfDay.value, const TimeOfDay(hour: 2, minute: 2));
|
|
|
|
// Restore to empty data will re-initialize to default values.
|
|
await tester.restoreFrom(TestRestorationData.empty);
|
|
expect(state.timeOfDay.value, const TimeOfDay(hour: 10, minute: 5));
|
|
});
|
|
|
|
testWidgets('call notifiers when value changes', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const RootRestorationScope(restorationId: 'root-child', child: _RestorableWidget()),
|
|
);
|
|
|
|
final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));
|
|
|
|
final notifyLog = <String>[];
|
|
|
|
state.timeOfDay.addListener(() {
|
|
notifyLog.add('hello world');
|
|
});
|
|
|
|
state.setProperties(() {
|
|
state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
|
|
});
|
|
expect(notifyLog.single, 'hello world');
|
|
notifyLog.clear();
|
|
await tester.pump();
|
|
|
|
// Does not notify when set to same value.
|
|
state.setProperties(() {
|
|
state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
|
|
});
|
|
|
|
expect(notifyLog, isEmpty);
|
|
});
|
|
});
|
|
|
|
testWidgets('correctly compares to other TimeOfDays', (WidgetTester tester) async {
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).compareTo(const TimeOfDay(hour: 1, minute: 0)),
|
|
lessThan(0),
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 20, minute: 0).compareTo(const TimeOfDay(hour: 20, minute: 1)),
|
|
lessThan(0),
|
|
);
|
|
expect(const TimeOfDay(hour: 0, minute: 0).compareTo(const TimeOfDay(hour: 0, minute: 0)), 0);
|
|
expect(
|
|
const TimeOfDay(hour: 1, minute: 0).compareTo(const TimeOfDay(hour: 0, minute: 0)),
|
|
greaterThan(0),
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 20, minute: 1).compareTo(const TimeOfDay(hour: 20, minute: 0)),
|
|
greaterThan(0),
|
|
);
|
|
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isBefore(const TimeOfDay(hour: 1, minute: 0)),
|
|
isTrue,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isBefore(const TimeOfDay(hour: 23, minute: 0)),
|
|
isTrue,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isBefore(const TimeOfDay(hour: 0, minute: 10)),
|
|
isTrue,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isBefore(const TimeOfDay(hour: 0, minute: 0)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 1, minute: 0).isBefore(const TimeOfDay(hour: 0, minute: 0)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 23, minute: 0).isBefore(const TimeOfDay(hour: 0, minute: 0)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 10).isBefore(const TimeOfDay(hour: 0, minute: 0)),
|
|
isFalse,
|
|
);
|
|
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isAfter(const TimeOfDay(hour: 1, minute: 0)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isAfter(const TimeOfDay(hour: 23, minute: 0)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isAfter(const TimeOfDay(hour: 0, minute: 10)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isAfter(const TimeOfDay(hour: 0, minute: 0)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 1, minute: 0).isAfter(const TimeOfDay(hour: 0, minute: 0)),
|
|
isTrue,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 23, minute: 0).isAfter(const TimeOfDay(hour: 0, minute: 0)),
|
|
isTrue,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 10).isAfter(const TimeOfDay(hour: 0, minute: 0)),
|
|
isTrue,
|
|
);
|
|
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isAtSameTimeAs(const TimeOfDay(hour: 1, minute: 0)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isAtSameTimeAs(const TimeOfDay(hour: 0, minute: 10)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 0).isAtSameTimeAs(const TimeOfDay(hour: 0, minute: 0)),
|
|
isTrue,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 1, minute: 0).isAtSameTimeAs(const TimeOfDay(hour: 0, minute: 0)),
|
|
isFalse,
|
|
);
|
|
expect(
|
|
const TimeOfDay(hour: 0, minute: 10).isAtSameTimeAs(const TimeOfDay(hour: 0, minute: 0)),
|
|
isFalse,
|
|
);
|
|
});
|
|
}
|
|
|
|
class _RestorableWidget extends StatefulWidget {
|
|
const _RestorableWidget();
|
|
|
|
@override
|
|
State<_RestorableWidget> createState() => _RestorableWidgetState();
|
|
}
|
|
|
|
class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMixin {
|
|
final RestorableTimeOfDay timeOfDay = RestorableTimeOfDay(const TimeOfDay(hour: 10, minute: 5));
|
|
|
|
@override
|
|
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
|
registerForRestoration(timeOfDay, 'time_of_day');
|
|
}
|
|
|
|
void setProperties(VoidCallback callback) {
|
|
setState(callback);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const SizedBox();
|
|
}
|
|
|
|
@override
|
|
String get restorationId => 'widget';
|
|
|
|
@override
|
|
void dispose() {
|
|
timeOfDay.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|