// Locks the sidebar invariant: destination icons sit at the same Y // whether the rail is collapsed or expanded. The header rows above the // destinations list have FIXED pixel heights (see _SidebarState), so // expanding the rail (a width-only change) must not shift any // destination vertically. This was a recurring regression source; this // test pins it. // // Expansion is driven via StudioApp(startSidebarExpanded: true), NOT a // hover gesture — hover mid-transition trips RenderFlex overflow in // widget tests (see widget_test.dart). With the flag the rail's // animation controller starts at value 1.0 (fully expanded), so no // animation needs to settle. import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:chain_studio/data/hub.dart'; import 'package:chain_studio/main.dart'; const _destinations = [ 'welcome', 'store', 'doctor', 'flows', 'audit', 'approvals', 'federation', ]; Map _destinationYs(WidgetTester tester) { final ys = {}; for (final id in _destinations) { final finder = find.byKey(ValueKey('sidebar-item-$id')); expect(finder, findsOneWidget, reason: 'destination "$id" must render'); ys[id] = tester.getTopLeft(finder).dy; } return ys; } void main() { testWidgets( 'sidebar destination Y positions are stable across rail expansion', (tester) async { // Collapsed (default). await tester.pumpWidget( const StudioApp( initialThemeMode: ThemeModeValue.system, initialLocale: Locale('en'), ), ); // pump (not pumpAndSettle): the app has long-lived timers/animations // that never "settle"; one frame is enough to lay the sidebar out. await tester.pump(const Duration(milliseconds: 100)); final collapsed = _destinationYs(tester); // Expanded: controller starts at 1.0 via the flag, hover disabled. await tester.pumpWidget( const StudioApp( initialThemeMode: ThemeModeValue.system, initialLocale: Locale('en'), startSidebarExpanded: true, ), ); await tester.pump(const Duration(milliseconds: 100)); final expanded = _destinationYs(tester); for (final id in _destinations) { expect( expanded[id], closeTo(collapsed[id]!, 0.5), reason: 'destination "$id" Y shifted on expansion ' '(${collapsed[id]} -> ${expanded[id]}) — the rows above the ' 'destinations list must keep fixed heights', ); } }, ); }