Add SRS app: results, shops, orders and users, wired to srs-api
Flutter web/PWA for the lottery agency, built on Riverpod and go_router,
talking to srs-api over HTTP (HttpApi, token kept in SharedPreferences).
- Results, Shops, Orders and Users tabs (Users for admins only), with
phone, tablet and desktop layouts and shared widgets and theme tokens
- Users: list with search and Active/Inactive filters; user detail with
shops, sign-in details, and an Access card
- WhatsApp results switch on each user (sendResults on
PATCH /api/users/:id). Shows the WhatsApp logo (Simple Icons, CC0,
drawn with flutter_svg), green only when results will really go out.
Can't be turned on without a phone number ("Add a phone number first"
opens the phone sheet), and is paused while the user is inactive. The
users list marks people who get results with the logo.
- Each Access switch has a one-line description; the explanation
paragraphs are gone
- Remove demo mode: the app always uses the real AP
test/support and is used only by tests.
151 lines
5.3 KiB
Dart
151 lines
5.3 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<void> boot(WidgetTester t) async {
|
|
t.view.physicalSize = const Size(430 * 2, 900 * 2);
|
|
t.view.devicePixelRatio = 2;
|
|
addTearDown(t.view.reset);
|
|
await t.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
apiProvider.overrideWithValue(FakeApi()),
|
|
startDateProvider.overrideWithValue(DateTime(2026, 10, 16)),
|
|
],
|
|
child: const SrsApp(),
|
|
),
|
|
);
|
|
await t.pumpAndSettle();
|
|
}
|
|
|
|
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);
|
|
});
|
|
}
|