Files
plezy/test/widgets/system_clock_test.dart
edde746 f0debe2c32 feat(ui): show a system-format clock on TV home and in the player
The clock renders through the existing formatClockTime helper driven by
MediaQuery.alwaysUse24HourFormatOf, so it follows the OS 12/24-hour
setting instead of introducing an app preference. It re-arms a one-shot
timer onto each wall-clock minute boundary rather than polling, and
resyncs on resume because a suspended process runs no timers.

The player header is shared by the mobile and desktop/TV controls, so one
insertion point covers every form factor: the player is fullscreen
everywhere, so it never has an OS clock to defer to. Home is the
exception and only gets one on TV, where a leanback app hides the system
clock; a phone status bar and a desktop menu bar already show the time.
2026-08-08 10:58:59 +02:00

85 lines
3.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:plezy/i18n/strings.g.dart';
import 'package:plezy/widgets/system_clock.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
LocaleSettings.setLocaleSync(AppLocale.en);
await initializeDateFormatting('en');
});
testWidgets('renders 24-hour time when the system asks for it', (tester) async {
await _pumpClock(tester, now: () => DateTime(2026, 8, 8, 18, 5), use24Hour: true);
expect(find.text('18:05'), findsOneWidget);
});
testWidgets('renders 12-hour time when the system asks for it', (tester) async {
await _pumpClock(tester, now: () => DateTime(2026, 8, 8, 18, 5), use24Hour: false);
// CLDR separates the day period with a narrow no-break space, so match the
// shape rather than pinning the separator codepoint.
expect(_clockText(tester), matches(RegExp(r'^6:05\s?PM$')));
});
testWidgets('follows the system format flipping while mounted', (tester) async {
DateTime now() => DateTime(2026, 8, 8, 18, 5);
await _pumpClock(tester, now: now, use24Hour: false);
expect(_clockText(tester), matches(RegExp(r'^6:05\s?PM$')));
await _pumpClock(tester, now: now, use24Hour: true);
expect(_clockText(tester), '18:05');
});
testWidgets('advances when the wall clock crosses a minute boundary', (tester) async {
var now = DateTime(2026, 8, 8, 18, 5, 30);
await _pumpClock(tester, now: () => now, use24Hour: true);
expect(find.text('18:05'), findsOneWidget);
// Nothing scheduled before the boundary: the label is still the old minute
// 29 seconds later.
now = DateTime(2026, 8, 8, 18, 5, 59);
await tester.pump(const Duration(seconds: 29));
expect(find.text('18:05'), findsOneWidget);
now = DateTime(2026, 8, 8, 18, 6, 0);
await tester.pump(const Duration(seconds: 1));
expect(find.text('18:06'), findsOneWidget);
// And it re-arms rather than firing once.
now = DateTime(2026, 8, 8, 18, 7, 0);
await tester.pump(const Duration(minutes: 1));
expect(find.text('18:07'), findsOneWidget);
});
testWidgets('stops ticking once it leaves the tree', (tester) async {
await _pumpClock(tester, now: () => DateTime(2026, 8, 8, 18, 5), use24Hour: true);
await tester.pumpWidget(const SizedBox.shrink());
// A surviving timer would trip the binding's pending-timer invariant.
await tester.pump(const Duration(minutes: 5));
expect(find.byType(SystemClock), findsNothing);
});
}
Future<void> _pumpClock(WidgetTester tester, {required DateTime Function() now, required bool use24Hour}) {
return tester.pumpWidget(
MediaQuery(
data: MediaQueryData(alwaysUse24HourFormat: use24Hour),
child: Directionality(
textDirection: TextDirection.ltr,
child: SystemClock(now: now),
),
),
);
}
String _clockText(WidgetTester tester) =>
tester.widget<Text>(find.descendant(of: find.byType(SystemClock), matching: find.byType(Text))).data!;