first commit

This commit is contained in:
2026-08-11 13:16:33 +05:30
parent 18531bf46b
commit c8350563d9
388 changed files with 69656 additions and 0 deletions

152
test/home_appbar_test.dart Normal file
View File

@@ -0,0 +1,152 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
/// Home's app bar carries the rider's identity **and the ON/OFF duty toggle**,
/// and it used to be the first sliver inside the page's `CustomScrollView` — so
/// it scrolled away the moment he looked at his second stop, taking the duty
/// control with it.
///
/// The fix is structural: the bar becomes the first child of a `Column` and the
/// scrollable takes the rest via `Expanded`. `_HomePageState` cannot be built in
/// a test (Get, SharedPreferences, Geolocator, live fetches), so — as in
/// `card_stripe_layout_test.dart` — these pin the *idiom* rather than the widget.
void main() {
const barKey = ValueKey('appbar');
const contentKey = ValueKey('tall-content');
// The default test viewport is 800×600 and the bar is 96 tall, so this point
// is always inside the scroll area below it. Gestures are aimed here rather
// than at the content's centre: the content is 2000pt tall, so its centre is
// off-screen and a drag there hit-tests against nothing.
const inScrollArea = Offset(400, 350);
/// The shape Home now uses.
Widget pinned({Future<void> Function()? onRefresh}) {
final scroller = CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: Container(key: contentKey, height: 2000, color: Colors.grey),
),
],
);
return MaterialApp(
home: Scaffold(
body: Stack(
children: [
Column(
children: [
Container(key: barKey, height: 96, color: Colors.white),
Expanded(
child: onRefresh == null
? scroller
: RefreshIndicator(onRefresh: onRefresh, child: scroller),
),
],
),
],
),
),
);
}
/// The shape it used to use — bar as a sliver.
Widget scrolling() {
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: Container(key: barKey, height: 96, color: Colors.white),
),
SliverToBoxAdapter(
child: Container(key: contentKey, height: 2000, color: Colors.grey),
),
],
),
),
);
}
testWidgets('as a sliver, the bar scrolls off the top', (tester) async {
await tester.pumpWidget(scrolling());
expect(find.byKey(barKey), findsOneWidget);
await tester.dragFrom(inScrollArea, const Offset(0, -300));
await tester.pumpAndSettle();
// Not merely pushed off-screen — a sliver that leaves the viewport and its
// cache extent is unmounted outright, so the duty toggle stopped existing
// rather than just moving. This is the behaviour being fixed.
expect(find.byKey(barKey), findsNothing,
reason: 'this is the behaviour that hid the duty toggle');
});
testWidgets('as a Column child, the bar does not move', (tester) async {
await tester.pumpWidget(pinned());
final before = tester.getRect(find.byKey(barKey));
final contentBefore = tester.getRect(find.byKey(contentKey)).top;
await tester.dragFrom(inScrollArea, const Offset(0, -300));
await tester.pumpAndSettle();
expect(tester.getRect(find.byKey(barKey)), before,
reason: 'the app bar is fixed');
// And the page really did scroll underneath it.
expect(tester.getRect(find.byKey(contentKey)).top,
lessThan(contentBefore - 200));
});
testWidgets('the bar stays put through a long scroll and back',
(tester) async {
await tester.pumpWidget(pinned());
final rect = tester.getRect(find.byKey(barKey));
for (final dy in [-600.0, -600.0, 400.0, -900.0, 1200.0]) {
await tester.dragFrom(inScrollArea, Offset(0, dy));
await tester.pumpAndSettle();
expect(tester.getRect(find.byKey(barKey)), rect);
}
});
testWidgets('pull to refresh still fires below the fixed bar',
(tester) async {
var fired = 0;
await tester.pumpWidget(pinned(onRefresh: () async {
fired++;
await Future<void>.delayed(const Duration(milliseconds: 50));
}));
final rect = tester.getRect(find.byKey(barKey));
await tester.flingFrom(inScrollArea, const Offset(0, 320), 1200);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(fired, 1, reason: 'the gesture still reaches the scroll view');
// The spinner must not drag the bar down with it, which is what the old
// unbounded-overscroll page did to the whole header.
expect(tester.getRect(find.byKey(barKey)), rect);
await tester.pumpAndSettle();
});
testWidgets('the refresh spinner comes down below the bar, not behind it',
(tester) async {
await tester.pumpWidget(pinned(onRefresh: () async {
await Future<void>.delayed(const Duration(milliseconds: 400));
}));
await tester.flingFrom(inScrollArea, const Offset(0, 320), 1200);
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
final spinner = find.byType(RefreshProgressIndicator);
expect(spinner, findsOneWidget);
// `edgeOffset` is no longer needed precisely because the scroll viewport now
// starts under the bar — so the indicator is already in clear space.
expect(tester.getRect(spinner).top,
greaterThanOrEqualTo(tester.getRect(find.byKey(barKey)).bottom - 0.01));
await tester.pumpAndSettle();
});
}