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( value: "off", itemBuilder: (ctx, v) => const Text("Off"), onChanged: (_) {}, popup: SelectPopup( items: SelectItemList( children: const [ SelectItemButton(value: "off", child: Text("Off")), SelectItemButton( value: "optional", child: Text("Optional"), ), SelectItemButton( 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)); await tester.pumpAndSettle(); final row = tester.getRect( find.ancestor( of: find.text("Required"), matching: find.byType(SelectItemButton), ), ); // 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)); await tester.pumpAndSettle(); for (final label in ["Off", "Optional", "Required"]) { final row = tester.getRect( find.ancestor( of: find.text(label), matching: find.byType(SelectItemButton), ), ); // 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)); 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)); await tester.pumpAndSettle(); for (final label in ["Off", "Optional", "Required"]) { final row = tester.getRect( find.ancestor( of: find.text(label), matching: find.byType(SelectItemButton), ), ); expect(row.left, greaterThanOrEqualTo(0.0), reason: label); expect(row.right, lessThanOrEqualTo(600.0), reason: label); } expect(tester.takeException(), isNull); }); }