216 lines
7.3 KiB
Dart
216 lines
7.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
|
|
import 'package:tasq/models/office.dart';
|
|
import 'package:tasq/models/profile.dart';
|
|
import 'package:tasq/models/ticket_message.dart';
|
|
import 'package:tasq/models/user_office.dart';
|
|
import 'package:tasq/providers/profile_provider.dart';
|
|
|
|
import 'package:tasq/providers/user_offices_provider.dart';
|
|
import 'package:tasq/providers/admin_user_provider.dart';
|
|
import 'package:tasq/providers/tickets_provider.dart';
|
|
import 'package:tasq/screens/admin/user_management_screen.dart';
|
|
|
|
class _FakeAdminController extends AdminUserController {
|
|
_FakeAdminController()
|
|
: super(SupabaseClient('http://localhost', 'test-key',
|
|
authOptions: const AuthClientOptions(autoRefreshToken: false)));
|
|
|
|
String? lastSetPasswordUserId;
|
|
String? lastSetPasswordValue;
|
|
|
|
String? lastSetLockUserId;
|
|
bool? lastSetLockedValue;
|
|
|
|
@override
|
|
Future<void> setPassword({
|
|
required String userId,
|
|
required String password,
|
|
}) async {
|
|
lastSetPasswordUserId = userId;
|
|
lastSetPasswordValue = password;
|
|
return Future.value();
|
|
}
|
|
|
|
@override
|
|
Future<void> setLock({required String userId, required bool locked}) async {
|
|
lastSetLockUserId = userId;
|
|
lastSetLockedValue = locked;
|
|
return Future.value();
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
final office = Office(id: 'office-1', name: 'HQ');
|
|
final profile = Profile(id: 'user-1', role: 'admin', fullName: 'Alice Admin');
|
|
|
|
ProviderScope buildApp({required List<Override> overrides}) {
|
|
return ProviderScope(
|
|
overrides: overrides,
|
|
child: const MaterialApp(home: Scaffold(body: UserManagementScreen())),
|
|
);
|
|
}
|
|
|
|
testWidgets('sanity - basic Text widgets render', (tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(home: Scaffold(body: Text('SANITY'))),
|
|
);
|
|
await tester.pump();
|
|
expect(find.text('SANITY'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Edit dialog pre-fills fields and shows actions', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(1280, 900));
|
|
addTearDown(() async => await tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(
|
|
buildApp(
|
|
overrides: [
|
|
currentProfileProvider.overrideWith((ref) => Stream.value(profile)),
|
|
profilesProvider.overrideWith((ref) => Stream.value([profile])),
|
|
officesProvider.overrideWith((ref) => Stream.value([office])),
|
|
userOfficesProvider.overrideWith(
|
|
(ref) => Stream.value([
|
|
UserOffice(userId: 'user-1', officeId: 'office-1'),
|
|
]),
|
|
),
|
|
ticketMessagesAllProvider.overrideWith((ref) => Stream.value(const <TicketMessage>[])),
|
|
isAdminProvider.overrideWith((ref) => true),
|
|
// Provide an AdminUserStatus so the dialog can show email/status immediately.
|
|
adminUserStatusProvider.overrideWithProvider(
|
|
FutureProvider.autoDispose.family<AdminUserStatus, String>(
|
|
(ref, id) async => AdminUserStatus(
|
|
email: 'alice@example.com',
|
|
bannedUntil: null,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
for (var i = 0; i < 20; i++) {
|
|
if (find.text('Alice Admin').evaluate().isNotEmpty) break;
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
}
|
|
|
|
expect(find.text('Alice Admin'), findsOneWidget);
|
|
|
|
// Open edit dialog
|
|
await tester.tap(find.text('Alice Admin'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
|
|
// Dialog should show and the Full name TextField should contain the profile name
|
|
final alert = find.byType(AlertDialog);
|
|
expect(alert, findsOneWidget);
|
|
|
|
final editable = find.descendant(
|
|
of: alert,
|
|
matching: find.byType(EditableText),
|
|
);
|
|
expect(editable, findsWidgets);
|
|
|
|
// The first EditableText inside the dialog is the Full name field
|
|
final fullNameEditable = editable.first;
|
|
final et = tester.widget<EditableText>(fullNameEditable);
|
|
expect(et.controller.text, equals('Alice Admin'));
|
|
|
|
// Role should show selected value
|
|
expect(
|
|
find.descendant(of: alert, matching: find.text('admin')),
|
|
findsWidgets,
|
|
);
|
|
|
|
// Reset password and Lock buttons must be visible
|
|
expect(
|
|
find.descendant(
|
|
of: alert,
|
|
matching: find.widgetWithText(OutlinedButton, 'Reset password'),
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.descendant(
|
|
of: alert,
|
|
matching: find.widgetWithText(OutlinedButton, 'Lock'),
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
});
|
|
|
|
testWidgets('Reset password and lock call admin controller', (tester) async {
|
|
final fake = _FakeAdminController();
|
|
|
|
await tester.binding.setSurfaceSize(const Size(1280, 900));
|
|
addTearDown(() async => await tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(
|
|
buildApp(
|
|
overrides: [
|
|
currentProfileProvider.overrideWith((ref) => Stream.value(profile)),
|
|
profilesProvider.overrideWith((ref) => Stream.value([profile])),
|
|
officesProvider.overrideWith((ref) => Stream.value([office])),
|
|
userOfficesProvider.overrideWith(
|
|
(ref) => Stream.value([
|
|
UserOffice(userId: 'user-1', officeId: 'office-1'),
|
|
]),
|
|
),
|
|
ticketMessagesAllProvider.overrideWith((ref) => Stream.value(const <TicketMessage>[])),
|
|
isAdminProvider.overrideWith((ref) => true),
|
|
adminUserStatusProvider.overrideWithProvider(
|
|
FutureProvider.autoDispose.family<AdminUserStatus, String>(
|
|
(ref, id) async => AdminUserStatus(
|
|
email: 'alice@example.com',
|
|
bannedUntil: null,
|
|
),
|
|
),
|
|
),
|
|
adminUserControllerProvider.overrideWithValue(fake),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
for (var i = 0; i < 20; i++) {
|
|
if (find.text('Alice Admin').evaluate().isNotEmpty) break;
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
}
|
|
|
|
// Open edit dialog (again)
|
|
await tester.tap(find.text('Alice Admin'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
|
|
// Tap Reset password
|
|
await tester.tap(find.widgetWithText(OutlinedButton, 'Reset password'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
|
|
// Enter new password in the nested dialog
|
|
final pwdField = find.byType(TextFormField).last;
|
|
await tester.enterText(pwdField, 'new-pass-123');
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
|
|
// Confirm update
|
|
await tester.tap(find.text('Update password'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
|
|
expect(fake.lastSetPasswordUserId, equals('user-1'));
|
|
expect(fake.lastSetPasswordValue, equals('new-pass-123'));
|
|
|
|
// Back in the main dialog - press Lock
|
|
await tester.tap(find.widgetWithText(OutlinedButton, 'Lock'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(fake.lastSetLockUserId, equals('user-1'));
|
|
expect(fake.lastSetLockedValue, isTrue);
|
|
});
|
|
}
|