mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-11 13:27:39 +01:00
Three independent cleanups ahead of the configurable meal schedule.
Pills: the row was a `Wrap`, so pills dropped onto a second line as soon as
they outgrew the width -- which they do even at four. Measured: a logged chip
is ~124px at Material defaults, so four need 522px against ~330px of phone
width. Now a `FittedBox(scaleDown)` over a `Row`, which scales the row down
uniformly, plus compact chip density so there is less to scale away.
A `Row` of `Expanded` children was the obvious alternative and is wrong: it
imposes a tight width, and a Chip narrower than avatar+padding+label clips its
label rather than shrinking -- measured at 10px per label, about one character,
while throwing no overflow error. The new regression test therefore asserts
both a shared baseline and a legible label width; it was confirmed to fail on
the old `Wrap` ("chip wrapped to a second line at 320.0") rather than passing
vacuously.
Card: the `Logged "<meal>".` line restated the description the user had just
typed. Removed, along with `TodayProgress.justLogged`, which nothing else read.
The kcal position, macros and streak stay.
refactor_claude_todo.md: the 250-line file-splitting refactor it tracked is
done -- `scripts/check_file_length.sh --all` exits 0 and an independent sweep
finds nothing over the cap. DESIGN_AUDIT_TODO.md is a separate, still-open
effort and stays.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159b4E5hh512jYUeaJVNxfM
132 lines
3.9 KiB
Dart
132 lines
3.9 KiB
Dart
import 'package:diet_guard_app/models/slot.dart';
|
|
import 'package:diet_guard_app/widgets/slot_selector_row.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
Future<void> _pump(
|
|
WidgetTester tester, {
|
|
required DateTime now,
|
|
Set<int> loggedSlots = const <int>{},
|
|
int? selectedSlot,
|
|
ValueChanged<int?>? onSlotSelected,
|
|
}) => tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: SlotSelectorRow(
|
|
now: now,
|
|
loggedSlots: loggedSlots,
|
|
selectedSlot: selectedSlot,
|
|
onSlotSelected: onSlotSelected ?? (_) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
void main() {
|
|
group('SlotSelectorRow', () {
|
|
testWidgets('renders exactly one chip per day slot', (tester) async {
|
|
await _pump(tester, now: DateTime(2026, 8, 14, 13));
|
|
|
|
expect(find.byType(ChoiceChip), findsNWidgets(daySlots().length));
|
|
for (final slot in daySlots()) {
|
|
expect(find.text(slotLabel(slot)), findsOneWidget);
|
|
}
|
|
});
|
|
|
|
// The "Snack" chip was removed on 2026-08-14; it was the only control
|
|
// that logged a meal against no slot at all.
|
|
testWidgets('has no Snack chip', (tester) async {
|
|
await _pump(tester, now: DateTime(2026, 8, 14, 13));
|
|
|
|
expect(find.text('Snack'), findsNothing);
|
|
expect(find.byIcon(Icons.fastfood), findsNothing);
|
|
});
|
|
|
|
testWidgets('tapping a chip reports that slot, never null', (
|
|
tester,
|
|
) async {
|
|
final selected = <int?>[];
|
|
await _pump(
|
|
tester,
|
|
now: DateTime(2026, 8, 14, 13),
|
|
onSlotSelected: selected.add,
|
|
);
|
|
|
|
for (final slot in daySlots()) {
|
|
await tester.tap(find.text(slotLabel(slot)));
|
|
}
|
|
|
|
expect(selected, daySlots());
|
|
expect(selected, isNot(contains(null)));
|
|
});
|
|
|
|
testWidgets('a logged slot gets a check avatar', (tester) async {
|
|
await _pump(
|
|
tester,
|
|
now: DateTime(2026, 8, 14, 13),
|
|
loggedSlots: {daySlots().first},
|
|
);
|
|
|
|
expect(find.byIcon(Icons.check), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('marks the selected slot', (tester) async {
|
|
final slot = daySlots().first;
|
|
await _pump(
|
|
tester,
|
|
now: DateTime(2026, 8, 14, 13),
|
|
selectedSlot: slot,
|
|
);
|
|
|
|
final chip = tester.widget<ChoiceChip>(
|
|
find.ancestor(
|
|
of: find.text(slotLabel(slot)),
|
|
matching: find.byType(ChoiceChip),
|
|
),
|
|
);
|
|
expect(chip.selected, isTrue);
|
|
});
|
|
|
|
// The row used to be a Wrap, which pushed the later pills onto a second
|
|
// line: a logged chip is ~124px at Material defaults, so even four need
|
|
// more width than a phone has. Both halves below matter -- a Row of
|
|
// Expanded children also passes the "one line" check while squeezing each
|
|
// label to ~10px (about one character) and throwing nothing at all.
|
|
testWidgets('keeps every chip on one legible row at phone widths', (
|
|
tester,
|
|
) async {
|
|
for (final width in <double>[320, 360, 412]) {
|
|
tester.view.physicalSize = Size(width, 800);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
// Every slot logged is the widest case: each chip carries a check
|
|
// avatar on top of its label.
|
|
await _pump(
|
|
tester,
|
|
now: DateTime(2026, 8, 14, 21),
|
|
loggedSlots: daySlots().toSet(),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull, reason: 'overflowed at $width');
|
|
|
|
final labels = daySlots()
|
|
.map((slot) => tester.getRect(find.text(slotLabel(slot))))
|
|
.toList();
|
|
for (final label in labels) {
|
|
expect(
|
|
label.top,
|
|
moreOrLessEquals(labels.first.top, epsilon: 0.5),
|
|
reason: 'chip wrapped to a second line at $width',
|
|
);
|
|
expect(
|
|
label.width,
|
|
greaterThan(20),
|
|
reason: 'label squeezed to ${label.width}px at $width',
|
|
);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|