srs/test/app_test.dart
Aswin Biju 749d18192a
All checks were successful
deploy / deploy (push) Successful in 4m58s
refactor: updated UI and other trivial fixes
2026-11-05 10:13:32 +05:30

330 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:srs_app/app.dart';
import 'support/fake_api.dart';
import 'package:srs_app/core/providers.dart';
Future<FakeApi> boot(WidgetTester t, {double width = 430}) async {
final api = FakeApi();
t.view.physicalSize = Size(width * 2, 900 * 2);
t.view.devicePixelRatio = 2;
addTearDown(t.view.reset);
await t.pumpWidget(
ProviderScope(
overrides: [
apiProvider.overrideWithValue(api),
startDateProvider.overrideWithValue(DateTime(2026, 10, 16)),
],
child: const SrsApp(),
),
);
await t.pumpAndSettle();
return api;
}
Future<void> signIn(WidgetTester t, String u, String p) async {
await t.enterText(find.byType(TextField).at(0), u);
await t.enterText(find.byType(TextField).at(1), p);
await t.tap(find.text('Sign in').last);
await t.pumpAndSettle(const Duration(milliseconds: 400));
}
void main() {
testWidgets('wrong password shows the error line', (t) async {
await boot(t);
expect(find.text('Welcome back'), findsOneWidget);
expect(
find.text('Forgot your password? Contact your administrator.'),
findsOneWidget,
);
await signIn(t, 'owner', 'bad-password');
expect(find.text('Wrong username or password'), findsOneWidget);
});
testWidgets('admin sees results, four tabs and can open a shop', (t) async {
await boot(t);
await signIn(t, 'owner', 'admin123');
expect(find.text('₹7,700'), findsOneWidget);
expect(find.text('Suvarna Keralam · SK-74'), findsOneWidget);
expect(find.text('Users'), findsOneWidget);
await t.tap(find.text('Shops').last);
await t.pumpAndSettle();
expect(find.text('Add shop'), findsOneWidget);
expect(find.text('180 tickets · 2 orders'), findsOneWidget);
});
testWidgets('normal user has three tabs and no Add shop button', (t) async {
await boot(t);
await signIn(t, 'kzk.staff', 'staff123');
expect(find.text('₹6,300'), findsOneWidget);
expect(find.text('Users'), findsNothing);
await t.tap(find.text('Shops').last);
await t.pumpAndSettle();
expect(find.text('Add shop'), findsNothing);
});
testWidgets('order detail lets a user delete the order after confirming', (
t,
) async {
await boot(t);
await signIn(t, 'kzk.staff', 'staff123');
await t.tap(find.text('Orders').last);
await t.pumpAndSettle();
expect(find.text('Orders'), findsWidgets);
await t.tap(find.textContaining('Photo').first);
await t.pumpAndSettle();
await t.scrollUntilVisible(
find.text('Delete order'),
400,
scrollable: find.byType(Scrollable).first,
);
await t.tap(find.text('Delete order'));
await t.pumpAndSettle();
expect(find.text('Delete this order?'), findsOneWidget);
await t.tap(find.text('Delete order').last);
await t.pumpAndSettle(const Duration(milliseconds: 500));
expect(find.text('Delete this order?'), findsNothing);
});
testWidgets('admin: user toggle and delete both ask for confirmation', (
t,
) async {
await boot(t);
await signIn(t, 'owner', 'admin123');
await t.tap(find.text('Users').last);
await t.pumpAndSettle();
await t.tap(find.text('skm.staff'));
await t.pumpAndSettle();
await t.tap(find.byType(Switch).first);
await t.pumpAndSettle();
expect(find.text('Deactivate skm.staff?'), findsOneWidget);
await t.tap(find.text('Cancel'));
await t.pumpAndSettle();
expect(t.widget<Switch>(find.byType(Switch).first).value, isTrue);
// To the end of the page, clear of the tab bar.
await t.drag(find.byType(Scrollable).first, const Offset(0, -600));
await t.pumpAndSettle();
await t.tap(find.text('Delete user'));
await t.pumpAndSettle();
expect(find.text('Delete skm.staff?'), findsOneWidget);
await t.tap(find.text('Delete user').last);
await t.pumpAndSettle(const Duration(milliseconds: 500));
expect(find.text('skm.staff'), findsNothing);
});
testWidgets('admin: results messages switch, and no phone means no switch', (
t,
) async {
await boot(t);
await signIn(t, 'owner', 'admin123');
await t.tap(find.text('Users').last);
await t.pumpAndSettle();
expect(find.byTooltip('Gets results on WhatsApp'), findsOneWidget);
await t.tap(find.text('kzk.staff'));
await t.pumpAndSettle();
final results = find.byType(Switch).last;
await t.scrollUntilVisible(
find.text('WhatsApp results'),
300,
scrollable: find.byType(Scrollable).first,
);
expect(t.widget<Switch>(results).value, isTrue);
expect(find.text('+91 90000 11111'), findsWidgets);
await t.tap(results);
await t.pumpAndSettle(const Duration(milliseconds: 400));
expect(find.text('WhatsApp results off'), findsOneWidget);
expect(t.widget<Switch>(find.byType(Switch).last).value, isFalse);
await t.tap(find.byTooltip('Back'));
await t.pumpAndSettle(const Duration(milliseconds: 400));
expect(find.byTooltip('Gets results on WhatsApp'), findsNothing);
await t.tap(find.text('skm.staff'));
await t.pumpAndSettle();
await t.scrollUntilVisible(
find.text('Add a phone number first'),
300,
scrollable: find.byType(Scrollable).first,
);
expect(t.widget<Switch>(find.byType(Switch).last).onChanged, isNull);
});
group('dates and orders', () {
testWidgets('opens on today, even before its results are out', (t) async {
await boot(t);
await signIn(t, 'owner', 'admin123');
expect(find.textContaining('Fri, 16 Oct 2026'), findsOneWidget);
await t.tap(find.byTooltip('Next draw'));
await t.pumpAndSettle();
expect(find.text('No result yet'), findsOneWidget);
expect(find.text('AWAITING RESULTS'), findsOneWidget);
expect(find.textContaining('3:00 PM'), findsNothing);
});
testWidgets('orders can be searched by shop, message or ending', (t) async {
await boot(t);
await signIn(t, 'owner', 'admin123');
await t.tap(find.text('Orders').last);
await t.pumpAndSettle();
expect(find.text('Kazhakkoottam'), findsWidgets);
await t.enterText(find.byType(TextField), 'ulloor');
await t.pumpAndSettle();
expect(find.text('Kazhakkoottam'), findsNothing);
await t.enterText(find.byType(TextField), 'nothing like this');
await t.pumpAndSettle();
expect(find.text('No matching orders'), findsOneWidget);
});
testWidgets('a slip sent with a note shows both', (t) async {
await boot(t);
await signIn(t, 'owner', 'admin123');
await t.tap(find.text('Orders').last);
await t.pumpAndSettle();
await t.tap(find.textContaining('Photo + text').first);
await t.pumpAndSettle();
expect(find.text('Handwritten slip'), findsOneWidget);
expect(find.textContaining('please add 4410'), findsOneWidget);
});
});
group('loading and refresh', () {
testWidgets('a new date shows a skeleton until its results arrive', (
t,
) async {
await boot(t);
await signIn(t, 'owner', 'admin123');
await t.tap(find.byTooltip('Previous draw'));
await t.pump();
await t.pump(const Duration(milliseconds: 50));
expect(find.bySemanticsLabel('Loading'), findsOneWidget);
// The draw buttons stay usable while the draw loads.
expect(find.byTooltip('Next draw'), findsWidgets);
await t.pumpAndSettle();
expect(find.bySemanticsLabel('Loading'), findsNothing);
});
testWidgets('pulling down reloads results and keeps them on screen', (
t,
) async {
final api = await boot(t);
await signIn(t, 'owner', 'admin123');
final before = api.resultsCalls;
await t.fling(find.text('₹7,700'), const Offset(0, 400), 1000);
await t.pump();
await t.pump(const Duration(milliseconds: 50));
expect(find.text('₹7,700'), findsOneWidget);
expect(find.bySemanticsLabel('Loading'), findsNothing);
await t.pumpAndSettle();
expect(api.resultsCalls, before + 1);
expect(find.text('₹7,700'), findsOneWidget);
});
testWidgets('a failed pull keeps the data and says so quickly', (t) async {
final api = await boot(t);
await signIn(t, 'owner', 'admin123');
api.failResults = true;
await t.fling(find.text('₹7,700'), const Offset(0, 400), 1000);
await t.pump();
await t.pump(const Duration(seconds: 1));
// One frame for the error to reach listeners, one for the toast.
await t.pump();
await t.pump(const Duration(milliseconds: 300));
expect(
find.text("Couldn't refresh. Check your connection and try again."),
findsOneWidget,
);
expect(find.text('₹7,700'), findsOneWidget);
api.failResults = false;
await t.pumpAndSettle(const Duration(seconds: 1));
});
testWidgets('a failed first load shows Retry, which shows the skeleton', (
t,
) async {
final api = await boot(t);
await signIn(t, 'owner', 'admin123');
api.failResults = true;
await t.tap(find.byTooltip('Previous draw'));
await t.pump();
await t.pump(const Duration(seconds: 1));
expect(find.text('Unable to reach the server.'), findsOneWidget);
api.failResults = false;
await t.tap(find.text('Retry'));
await t.pump();
await t.pump(const Duration(milliseconds: 50));
expect(find.text('Retry'), findsNothing);
await t.pumpAndSettle();
expect(find.text('Unable to reach the server.'), findsNothing);
expect(find.text('Retry'), findsNothing);
});
});
group('automatic refresh', () {
Future<void> leaveAndReturn(WidgetTester t) async {
final b = t.binding;
b.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
b.handleAppLifecycleStateChanged(AppLifecycleState.hidden);
b.handleAppLifecycleStateChanged(AppLifecycleState.paused);
b.handleAppLifecycleStateChanged(AppLifecycleState.hidden);
b.handleAppLifecycleStateChanged(AppLifecycleState.inactive);
b.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
await t.pump();
await t.pump(const Duration(milliseconds: 400));
}
testWidgets('coming back reloads, but not more than once per 30 s', (
t,
) async {
final api = await boot(t);
await signIn(t, 'owner', 'admin123');
final start = api.resultsCalls;
await leaveAndReturn(t);
expect(api.resultsCalls, start + 1);
expect(find.text('₹7,700'), findsOneWidget);
await leaveAndReturn(t);
expect(api.resultsCalls, start + 1);
await t.pump(const Duration(seconds: 31));
await leaveAndReturn(t);
expect(api.resultsCalls, start + 2);
});
testWidgets('Orders reloads every minute, other screens do not', (t) async {
final api = await boot(t);
await signIn(t, 'owner', 'admin123');
await t.tap(find.text('Orders').last);
await t.pumpAndSettle();
final orders = api.ordersCalls;
final results = api.resultsCalls;
await t.pump(const Duration(seconds: 61));
await t.pump(const Duration(milliseconds: 400));
expect(api.ordersCalls, greaterThan(orders));
await t.tap(find.text('Shops').last);
await t.pumpAndSettle();
final after = api.ordersCalls;
await t.pump(const Duration(seconds: 61));
await t.pump(const Duration(milliseconds: 400));
expect(api.ordersCalls, after);
expect(api.resultsCalls, results);
});
testWidgets('a failed background reload is silent', (t) async {
final api = await boot(t);
await signIn(t, 'owner', 'admin123');
api.failResults = true;
await leaveAndReturn(t);
await t.pump(const Duration(seconds: 1));
expect(find.text('₹7,700'), findsOneWidget);
expect(find.textContaining("Couldn't refresh"), findsNothing);
api.failResults = false;
await t.pumpAndSettle(const Duration(seconds: 1));
});
});
}