halkyon/packages/halkyon_ui/test/widget/day_calendar_icon_test.dart

130 lines
3.9 KiB
Dart
Raw Normal View History

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<bool>)> 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<DigitInk> 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',
);
}
});
}
}