test(studio): lock sidebar destination Y-stability on rail expansion

Adds a widget test asserting each destination icon keeps its Y position
whether the rail is collapsed or expanded — a recurring regression. To
drive expansion without a hover gesture (which trips RenderFlex overflow
mid-transition), a test-only startSidebarExpanded flag threads
StudioApp -> StudioShell -> _Sidebar (controller starts at 1.0, hover
disabled); _SidebarItems get stable ValueKeys. analyze clean; new test
and the existing smoke test pass.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-06-19 21:30:02 +02:00
parent d66f46f133
commit 36a8252f67
2 changed files with 103 additions and 5 deletions

77
test/sidebar_test.dart Normal file
View file

@ -0,0 +1,77 @@
// 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 = <String>[
'welcome',
'store',
'doctor',
'flows',
'audit',
'approvals',
'federation',
];
Map<String, double> _destinationYs(WidgetTester tester) {
final ys = <String, double>{};
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',
);
}
},
);
}