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
This commit is contained in:
ImBenji
2026-09-23 18:49:21 +01:00
co-authored by Claude Opus 5.5
commit b269201919
117 changed files with 26944 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
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 _select() => Select<String>(
value: "off",
itemBuilder: (ctx, v) => const Text("Off"),
onChanged: (_) {},
popup: SelectPopup(
items: SelectItemList(
children: const [
SelectItemButton<String>(value: "off", child: Text("Off")),
SelectItemButton<String>(
value: "optional",
child: Text("Optional"),
),
SelectItemButton<String>(
value: "required",
child: Text("Required"),
),
],
),
),
);
Widget _host({required Alignment where, double overlayWidth = 600}) =>
Directionality(
textDirection: TextDirection.ltr,
child: GarageTheme(
data: ThemeData(colorScheme: testScheme, density: const Density()),
child: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: overlayWidth,
height: 400,
child: Overlay(
initialEntries: [
OverlayEntry(
builder: (_) => Align(
alignment: where,
// narrow, the way a ghost select showing "Off" is narrow
child: SizedBox(width: 70, child: _select()),
),
),
],
),
),
),
),
);
void main() {
testWidgets("it sizes to its widest row, not to the room available", (
tester,
) async {
// given a ceiling, a ListView based popup filled the ceiling - a 70px
// trigger opening a popup most of 600px wide.
await tester.pumpWidget(_host(where: Alignment.topLeft));
await tester.tap(find.byType(Select<String>));
await tester.pumpAndSettle();
final row = tester.getRect(
find.ancestor(
of: find.text("Required"),
matching: find.byType(SelectItemButton<String>),
),
);
// wide enough for the longest option and its tick, nowhere near the 600
// it was offered
expect(row.width, greaterThan(70));
expect(row.width, lessThan(300));
expect(tester.takeException(), isNull);
});
testWidgets("a narrow trigger near an edge still gets a readable popup", (
tester,
) async {
// the sliver: clamped to the room on the side the placement picked by
// testing the FLOOR, the popup came out one character wide.
await tester.pumpWidget(_host(where: Alignment.topRight));
await tester.tap(find.byType(Select<String>));
await tester.pumpAndSettle();
for (final label in ["Off", "Optional", "Required"]) {
final row = tester.getRect(
find.ancestor(
of: find.text(label),
matching: find.byType(SelectItemButton<String>),
),
);
// one line each, not a column of letters
expect(row.height, lessThan(40), reason: label);
// and never narrower than the trigger it came out of
expect(row.width, greaterThanOrEqualTo(70.0), reason: label);
}
expect(tester.takeException(), isNull);
});
testWidgets("the popup grows past a narrow trigger to fit its rows", (
tester,
) async {
await tester.pumpWidget(_host(where: Alignment.topLeft));
await tester.tap(find.byType(Select<String>));
await tester.pumpAndSettle();
// the row that wrapped onto two lines when the popup was pinned to a
// 70px trigger
final optional = tester.getSize(find.text("Optional"));
expect(optional.height, lessThan(30));
expect(tester.takeException(), isNull);
});
testWidgets("and it stays inside the overlay when the trigger is at the "
"right edge", (tester) async {
// the case that broke: a select hard against the right of a settings row.
// Left aligned to the trigger, a wider popup runs off the edge and gets
// clipped by whatever bounds it - a square corner and rows cut mid-word.
await tester.pumpWidget(_host(where: Alignment.topRight));
await tester.tap(find.byType(Select<String>));
await tester.pumpAndSettle();
for (final label in ["Off", "Optional", "Required"]) {
final row = tester.getRect(
find.ancestor(
of: find.text(label),
matching: find.byType(SelectItemButton<String>),
),
);
expect(row.left, greaterThanOrEqualTo(0.0), reason: label);
expect(row.right, lessThanOrEqualTo(600.0), reason: label);
}
expect(tester.takeException(), isNull);
});
}