import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:halkyon_ui/halkyon_ui.dart'; import 'package:material_ui/material_ui.dart'; import '../support/test_app.dart'; /// Every Halkyon app's frame around its tabs: a rail with the app's icon /// from medium windows on, a floating bar below, a tick on every choice. void main() { /// A 1 × 1 PNG, the app's icon in these tests. final icon = MemoryImage( Uint8List.fromList(const [ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, // 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0B, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x60, 0x00, 0x02, 0x00, 0x00, 0x05, 0x00, 0x01, 0x7A, 0x5E, 0xAB, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]), ); /// The frame in a window [width] wide, on tab [selected]; the tabs chosen /// go into [chosen]. Future show( WidgetTester tester, { required double width, required List chosen, int selected = 1, }) async { tester.view.physicalSize = Size(width, 800); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); await tester.pumpWidget( testApp( NavigationFrame( appName: 'Halkyon Test', icon: icon, selectedIndex: selected, onSelected: chosen.add, destinations: const [ (label: 'Home', icon: Icon(Icons.home), selectedIcon: null), (label: 'Maps', icon: Icon(Icons.map), selectedIcon: null), (label: 'Profile', icon: Icon(Icons.person), selectedIcon: null), ], child: const Scaffold(body: Text('The tab')), ), ), ); } /// The ticks the platform was asked for while [act] ran. Future> ticks( WidgetTester tester, Future Function() act, ) async { final haptics = []; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( SystemChannels.platform, (call) async { if (call.method == 'HapticFeedback.vibrate') { haptics.add(call.arguments); } return null; }, ); addTearDown( () => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( SystemChannels.platform, null, ), ); await act(); return haptics; } testWidgets('below medium windows, a bar floats above the foot', ( tester, ) async { await show(tester, width: 400, chosen: []); expect(find.byType(FloatingNavigationBar), findsOneWidget); expect(find.byType(NavigationRail), findsNothing); expect(find.text('The tab'), findsOneWidget); }); testWidgets("from medium windows on, a rail with the app's icon on top, " 'which leads to the first tab', (tester) async { final chosen = []; await show(tester, width: 800, chosen: chosen); expect(find.byType(NavigationRail), findsOneWidget); expect(find.byType(NavigationBar), findsNothing); expect(find.bySemanticsLabel('Halkyon Test'), findsOneWidget); await tester.tap(find.byTooltip('Home')); await tester.pump(); expect(chosen, [0]); }); testWidgets('every choice on the bar ticks, lightly', (tester) async { final chosen = []; await show(tester, width: 400, chosen: chosen); final haptics = await ticks(tester, () async { await tester.tap(find.text('Profile')); await tester.pump(); }); expect(haptics, ['HapticFeedbackType.selectionClick']); expect(chosen, [2]); }); testWidgets('every choice on the rail ticks, lightly', (tester) async { final chosen = []; await show(tester, width: 800, chosen: chosen); final haptics = await ticks(tester, () async { await tester.tap(find.text('Maps')); await tester.pump(); }); expect(haptics, ['HapticFeedbackType.selectionClick']); expect(chosen, [1]); }); testWidgets('the window crossing from compact to medium and back swaps ' 'the bar for the rail', (tester) async { await show(tester, width: 400, chosen: []); for (final width in const [800.0, 400.0]) { tester.view.physicalSize = Size(width, 800); await tester.pumpAndSettle(); } expect(find.byType(FloatingNavigationBar), findsOneWidget); expect(find.text('The tab'), findsOneWidget); }); testWidgets('the bar steps aside where a screen has a bar of its own, ' "leaving the system's bar its room", (tester) async { tester.view.physicalSize = const Size(400, 800); tester.view.devicePixelRatio = 1; tester.view.padding = const FakeViewPadding(bottom: 24); addTearDown(tester.view.reset); await tester.pumpWidget( testApp( NavigationFrame( appName: 'Halkyon Test', icon: icon, selectedIndex: 0, onSelected: (_) {}, hidesNavigationBar: true, destinations: const [ (label: 'Home', icon: Icon(Icons.home), selectedIcon: null), (label: 'Maps', icon: Icon(Icons.map), selectedIcon: null), ], child: const Scaffold(body: Text('The tab')), ), ), ); expect(find.byType(FloatingNavigationBar), findsNothing); expect(tester.getBottomLeft(find.byType(Scaffold).last).dy, 800 - 24); }); testWidgets('a status stands above the bar below medium windows, and at ' "the foot of the screen's panel from there on", (tester) async { for (final width in const [400.0, 800.0]) { tester.view.physicalSize = Size(width, 800); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); await tester.pumpWidget( testApp( NavigationFrame( appName: 'Halkyon Test', icon: icon, selectedIndex: 0, onSelected: (_) {}, status: const Text('Recording'), destinations: const [ (label: 'Home', icon: Icon(Icons.home), selectedIcon: null), (label: 'Maps', icon: Icon(Icons.map), selectedIcon: null), ], child: const Scaffold(body: Text('The tab')), ), ), ); await tester.pumpAndSettle(); final status = tester.getBottomLeft(find.text('Recording')).dy; final tab = tester.getTopLeft(find.text('The tab')).dy; expect(status, greaterThan(tab), reason: '$width'); if (width < 600) { expect( status, lessThan(tester.getTopLeft(find.byType(NavigationBar)).dy), ); } } }); testWidgets("the keyboard leaves the frame's tab its size: the tab's own " 'screen decides whether it makes way, as one with a field floating ' "over it doesn't", (tester) async { tester.view.physicalSize = const Size(400, 800); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); Widget frame({required bool screenResizes}) => testApp( NavigationFrame( appName: 'Halkyon Test', icon: icon, selectedIndex: 0, onSelected: (_) {}, destinations: const [ (label: 'Home', icon: Icon(Icons.home), selectedIcon: null), (label: 'Maps', icon: Icon(Icons.map), selectedIcon: null), ], child: Scaffold( resizeToAvoidBottomInset: screenResizes, body: const SizedBox.expand(key: Key('tab')), ), ), ); await tester.pumpWidget(frame(screenResizes: false)); final before = tester.getSize(find.byKey(const Key('tab'))); tester.view.viewInsets = const FakeViewPadding(bottom: 300); addTearDown(tester.view.resetViewInsets); await tester.pump(); expect(tester.getSize(find.byKey(const Key('tab'))), before); await tester.pumpWidget(frame(screenResizes: true)); expect( tester.getSize(find.byKey(const Key('tab'))).height, lessThan(before.height), reason: 'a screen that resizes still makes way', ); }); }