import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:halkyon_ui/halkyon_ui.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:material_ui/material_ui.dart'; /// Where ink is drawn, in the calendar's 24 units. typedef DigitInk = ({double left, double top, double right, double bottom}); void main() { setUpAll(() async { // The real fonts, as a phone draws them: the test font's square glyphs // say nothing about where the digits' ink stands. final noto = FontLoader(appFontFamily) ..addFont(rootBundle.load('assets/fonts/NotoSans-Variable.ttf')); final lucide = FontLoader('packages/lucide_icons_flutter/Lucide') ..addFont( rootBundle.load('packages/lucide_icons_flutter/assets/lucide.ttf'), ); await Future.wait([noto.load(), lucide.load()]); }); /// The alpha channel of [child] drawn at [size], ten pixels a unit. Future<(int, List)> draw( WidgetTester tester, Widget child, double size, ) async { final key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: IconTheme( data: IconThemeData(size: size, color: const Color(0xFF000000)), child: Center( child: RepaintBoundary(key: key, child: child), ), ), ), ); final boundary = key.currentContext!.findRenderObject()! as RenderRepaintBoundary; final drawn = await tester.runAsync(() async { final image = await boundary.toImage(pixelRatio: 240 / size); final bytes = await image.toByteData(); final width = image.width; image.dispose(); final rgba = bytes!.buffer.asUint8List(); return ( width, [for (var i = 3; i < rgba.length; i += 4) rgba[i] > 128], ); }); return drawn!; } /// The ink of the day [day] at [size], apart from the calendar's own. Future digits(WidgetTester tester, int day, double size) async { final (width, calendar) = await draw( tester, SizedBox.square( dimension: size, child: const Icon(LucideIcons.calendar), ), size, ); final (_, icon) = await draw(tester, DayCalendarIcon(day: day), size); var left = width; var top = width; var right = -1; var bottom = -1; for (var i = 0; i < icon.length; i++) { if (icon[i] && !calendar[i]) { final x = i % width; final y = i ~/ width; if (x < left) { left = x; } if (x > right) { right = x; } if (y < top) { top = y; } if (y > bottom) { bottom = y; } } } return ( left: left / 10, top: top / 10, right: (right + 1) / 10, bottom: (bottom + 1) / 10, ); } for (final size in [16.0, 18.0, 20.0, 24.0, 32.0]) { testWidgets('at $size pixels, the day stands centred in the body, below ' "its top bar (the developer's review, 7 Nov 2026)", (tester) async { final ink = await digits(tester, 7, size); // Lucide's font draws the body's inside from 10 to 20 units. expect( (ink.top + ink.bottom) / 2, moreOrLessEquals(15, epsilon: 0.5), ); expect(ink.top, greaterThan(10)); expect(ink.bottom, lessThan(20)); }); testWidgets('at $size pixels, two digits fit between the sides', ( tester, ) async { for (final day in [10, 20, 28, 31]) { final ink = await digits(tester, day, size); // The sides' strokes end at 4 and begin at 20 units; a unit clear. expect(ink.left, greaterThan(5), reason: '$day'); expect(ink.right, lessThan(19), reason: '$day'); expect( (ink.left + ink.right) / 2, moreOrLessEquals(12, epsilon: 0.75), reason: '$day', ); } }); } }