Files
Garage-SDKs/garage_ui/test/field_error_test.dart
T
ImBenjiandClaude Opus 5.5 b269201919 The Garage SDKs, in the open
garage_auth, garage_entitlements, garage_iap and garage_ui, moved out of
Garage-Services and Metro-Map-Maker into one public repo. MIT, one readme,
docs under docs/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013F4NWNvYcdeSgqbWMT1VQ7
2026-09-23 18:49:21 +01:00

132 lines
4.0 KiB
Dart

import "package:flutter/widgets.dart";
import "package:flutter_test/flutter_test.dart";
import "package:garage_ui/garage_ui.dart";
import "support/test_scheme.dart";
Widget _host(Widget child) => Directionality(
textDirection: TextDirection.ltr,
child: GarageTheme(
data: ThemeData(colorScheme: testScheme),
child: Center(child: SizedBox(width: 400, child: child)),
),
);
Widget _row({String? error}) => _host(
SettingsRow(
key: const Key("row"),
label: "Name",
error: error,
field: const TextField(),
),
);
void main() {
testWidgets("the label's aside renders beside it, not instead of it", (
tester,
) async {
await tester.pumpWidget(
_host(
const SettingsRow(
label: "Name",
action: Text("Required"),
field: TextField(),
),
),
);
expect(find.text("Name"), findsOneWidget);
expect(find.text("Required"), findsOneWidget);
// on the label's line, and at the FAR right of its half of the row -
// spaceBetween, the way ProductField does it in the onboarding flow.
final label = tester.getRect(find.text("Name"));
final tag = tester.getRect(find.text("Required"));
final field = tester.getRect(find.byType(TextField));
expect((tag.center.dy - label.center.dy).abs(), lessThan(2));
// tucked against the label rather than pushed to the far end of the
// column - one gap between them, not the whole remaining width.
expect(tag.left - label.right, lessThan(12));
expect(tag.left, greaterThan(label.right - 1));
expect(tag.right, lessThan(field.left));
});
testWidgets("the aside reads as a tag, not as a second label", (
tester,
) async {
await tester.pumpWidget(
_host(
const SettingsRow(
label: "Name",
action: Text("Required"),
field: TextField(),
),
),
);
final tag = tester.widget<Text>(find.text("Required"));
final style = DefaultTextStyle.of(
tester.element(find.text("Required")),
).style.merge(tag.style);
final label = tester.widget<Text>(find.text("Name"));
expect(style.fontSize, const Density().textXxs);
expect(style.color, testScheme.mutedForeground);
expect(style.fontSize, lessThan(label.style!.fontSize!));
});
testWidgets("a property row puts it in the same place", (tester) async {
await tester.pumpWidget(
_host(
PropertyRow(
label: "Price",
scheme: testScheme,
action: const Text("Required"),
child: const TextField(),
),
),
);
final label = tester.getRect(find.text("Price"));
final tag = tester.getRect(find.text("Required"));
final field = tester.getRect(find.byType(TextField));
expect(tag.left - label.right, lessThan(12));
expect(tag.right, lessThan(field.left));
});
testWidgets("the row eases open rather than jumping", (tester) async {
await tester.pumpWidget(_row());
await tester.pumpAndSettle();
final bare = tester.getSize(find.byKey(const Key("row"))).height;
await tester.pumpWidget(_row(error: "nope"));
await tester.pump(); // the frame the change lands on
final atStart = tester.getSize(find.byKey(const Key("row"))).height;
await tester.pump(const Duration(milliseconds: 90));
final midway = tester.getSize(find.byKey(const Key("row"))).height;
await tester.pumpAndSettle();
final settled = tester.getSize(find.byKey(const Key("row"))).height;
expect(settled, greaterThan(bare));
expect(midway, greaterThan(atStart));
expect(midway, lessThan(settled));
});
testWidgets("the outline fades in", (tester) async {
await tester.pumpWidget(_row());
await tester.pumpAndSettle();
await tester.pumpWidget(_row(error: "nope"));
await tester.pump();
await tester.pump(const Duration(milliseconds: 90));
final fades = tester
.widgetList<FadeTransition>(find.byType(FadeTransition))
.map((f) => f.opacity.value)
.toList();
expect(fades.any((v) => v > 0.0 && v < 1.0), isTrue);
});
}