import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:krow_worker_app/core/theme.dart'; import 'package:krow_worker_app/widgets/common.dart'; import 'package:krow_worker_app/widgets/glass_segments.dart'; import 'package:krow_worker_app/widgets/krow_nav_bar.dart'; Widget wrap(Widget child) => MaterialApp( theme: buildKrowTheme(), home: Scaffold(body: child), ); void main() { group('KrowNavBar', () { const items = [ KrowNavItem(icon: Icons.home, label: 'Today'), KrowNavItem(icon: Icons.work, label: 'Shifts'), KrowNavItem(icon: Icons.timer, label: 'Clock', showDot: true), ]; testWidgets('renders every label', (tester) async { await tester.pumpWidget( wrap(KrowNavBar(items: items, currentIndex: 0, onSelect: (_) {})), ); expect(find.text('Today'), findsOneWidget); expect(find.text('Shifts'), findsOneWidget); expect(find.text('Clock'), findsOneWidget); }); testWidgets('reports the tapped index', (tester) async { var selected = -1; await tester.pumpWidget( wrap( KrowNavBar( items: items, currentIndex: 0, onSelect: (i) => selected = i, ), ), ); await tester.tap(find.text('Clock')); expect(selected, 2); }); testWidgets('announces a tab that needs attention', (tester) async { await tester.pumpWidget( wrap(KrowNavBar(items: items, currentIndex: 0, onSelect: (_) {})), ); // The dot is not readable, so the state has to reach the semantics tree. expect(find.bySemanticsLabel('Clock, needs attention'), findsOneWidget); expect(find.bySemanticsLabel('Today'), findsOneWidget); }); testWidgets('the indicator travels rather than jumping', (tester) async { var index = 0; await tester.pumpWidget( wrap( StatefulBuilder( builder: (context, setState) => KrowNavBar( items: items, currentIndex: index, onSelect: (i) => setState(() => index = i), ), ), ), ); Rect pillRect() => tester.getRect(find.byKey(KrowNavBar.indicatorKey)); final start = pillRect().center.dx; await tester.tap(find.text('Clock')); await tester.pump(); await tester.pump(const Duration(milliseconds: 120)); // Part way through it should be between the two tabs, not already there. final middle = pillRect().center.dx; expect(middle, greaterThan(start)); await tester.pumpAndSettle(); final end = pillRect().center.dx; expect(end, greaterThan(middle)); }); }); group('StatSlabRow', () { // Every real use sits in a scrolling column, where the height is // unbounded. Testing it against a bounded Scaffold body hides exactly the // layout errors this row is prone to. Widget inScrollable(Widget child) => wrap(ListView(children: [const SizedBox(height: 8), child])); testWidgets('lays out inside a scrollable', (tester) async { await tester.pumpWidget( inScrollable( const StatSlabRow( slabs: [ StatSlab('Clock in', '6:01 PM'), StatSlab('Clock out', '11:50 PM'), StatSlab('Hours', '5h 19m'), ], ), ), ); expect(tester.takeException(), isNull); // A row that tried to fill unbounded height would be enormous. expect(tester.getSize(find.byType(StatSlabRow)).height, lessThan(120)); }); testWidgets('keeps every card the same height, however long the text', ( tester, ) async { await tester.pumpWidget( inScrollable( const StatSlabRow( slabs: [ // The middle one is what used to wrap on both lines and push // its card taller than the two beside it. StatSlab('Clock in', '6:01 PM'), StatSlab('Clock out', '11:50 PM'), StatSlab('Hours', '5h 19m'), ], ), ), ); final heights = tester .widgetList(find.byType(StatSlab)) .map((slab) => tester.getSize(find.byWidget(slab)).height) .toSet(); expect(heights.length, 1, reason: 'cards should be one height'); }); testWidgets('scales long values down instead of wrapping', (tester) async { await tester.pumpWidget( inScrollable( const StatSlabRow( slabs: [ StatSlab('Paid', r'$1,234.56'), StatSlab('Rate', r'$21.00/hr'), StatSlab('Hours', '5h 19m'), ], ), ), ); for (final text in tester.widgetList(find.byType(Text))) { expect(text.maxLines, 1); } }); }); group('GlassSegments', () { testWidgets('reports the tapped segment', (tester) async { String? picked; await tester.pumpWidget( wrap( GlassSegments( segments: const [('a', 'Week'), ('b', 'Month'), ('c', 'Year')], value: 'a', onChanged: (v) => picked = v, ), ), ); await tester.tap(find.text('Year')); expect(picked, 'c'); }); testWidgets('ignores a tap on the segment already selected', ( tester, ) async { var calls = 0; await tester.pumpWidget( wrap( GlassSegments( segments: const [('a', 'Week'), ('b', 'Month')], value: 'a', onChanged: (_) => calls++, ), ), ); await tester.tap(find.text('Week')); expect(calls, 0); await tester.tap(find.text('Month')); expect(calls, 1); }); }); }