tasq/test/profile_screen_test.dart

233 lines
6.8 KiB
Dart

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tasq/models/office.dart';
import 'package:tasq/models/profile.dart';
import 'package:tasq/models/user_office.dart';
import 'package:tasq/providers/profile_provider.dart';
import 'package:tasq/providers/tickets_provider.dart';
import 'package:tasq/providers/user_offices_provider.dart';
import 'package:tasq/providers/auth_provider.dart';
import 'package:tasq/screens/profile/profile_screen.dart';
import 'package:tasq/widgets/multi_select_picker.dart';
import 'package:awesome_snackbar_content/awesome_snackbar_content.dart';
class _FakeProfileController implements ProfileController {
String? lastFullName;
String? lastPassword;
@override
Future<void> updateFullName({
required String userId,
required String fullName,
}) async {
lastFullName = fullName;
}
@override
Future<void> updatePassword(String password) async {
lastPassword = password;
}
@override
Future<String> uploadAvatar({
required String userId,
required Uint8List bytes,
required String fileName,
}) async {
return 'https://example.com/avatar.jpg';
}
@override
Future<String> uploadFacePhoto({
required String userId,
required Uint8List bytes,
required String fileName,
}) async {
return 'https://example.com/face.jpg';
}
@override
Future<Uint8List?> downloadFacePhoto(String userId) async {
return null;
}
}
class _FakeUserOfficesController implements UserOfficesController {
final List<Map<String, String>> assigned = [];
@override
Future<void> assignUserOffice({
required String userId,
required String officeId,
}) async {
assigned.add({'userId': userId, 'officeId': officeId});
}
@override
Future<void> removeUserOffice({
required String userId,
required String officeId,
}) async {
assigned.removeWhere(
(e) => e['userId'] == userId && e['officeId'] == officeId,
);
}
}
void main() {
final officeA = Office(id: 'office-1', name: 'HQ');
final officeB = Office(id: 'office-2', name: 'Branch');
final profile = Profile(id: 'user-1', role: 'standard', fullName: 'Paola');
ProviderScope buildApp({required List<Override> overrides}) {
return ProviderScope(
overrides: [
currentUserIdProvider.overrideWithValue(profile.id),
sessionProvider.overrideWithValue(null),
...overrides,
],
child: const MaterialApp(home: Scaffold(body: ProfileScreen())),
);
}
testWidgets('renders fields and sections', (tester) async {
await tester.pumpWidget(
buildApp(
overrides: [
currentProfileProvider.overrideWith((ref) => Stream.value(profile)),
officesProvider.overrideWith(
(ref) => Stream.value([officeA, officeB]),
),
userOfficesProvider.overrideWith(
(ref) => Stream.value(const <UserOffice>[]),
),
],
),
);
await tester.pumpAndSettle();
expect(find.text('My Profile'), findsOneWidget);
expect(find.text('Account details'), findsOneWidget);
expect(find.widgetWithText(TextFormField, 'Full name'), findsOneWidget);
expect(
find.byWidgetPredicate(
(w) => w is MultiSelectPicker<Office> && w.label == 'Offices',
),
findsOneWidget,
);
});
testWidgets('save details calls profile controller', (tester) async {
final fake = _FakeProfileController();
await tester.pumpWidget(
buildApp(
overrides: [
currentProfileProvider.overrideWith((ref) => Stream.value(profile)),
officesProvider.overrideWith((ref) => Stream.value([officeA])),
userOfficesProvider.overrideWith(
(ref) => Stream.value(const <UserOffice>[]),
),
profileControllerProvider.overrideWithValue(fake),
],
),
);
await tester.pumpAndSettle();
await tester.enterText(
find.widgetWithText(TextFormField, 'Full name'),
'New Name',
);
await tester.ensureVisible(find.text('Save details'));
await tester.tap(find.text('Save details'));
await tester.pumpAndSettle();
expect(fake.lastFullName, equals('New Name'));
// should show a success snackbar using the awesome_snackbar_content package
// (AwesomeSnackbarContent uses its own SVG icons, not Material Icons)
expect(find.byType(AwesomeSnackbarContent), findsOneWidget);
});
testWidgets('save offices assigns selected office', (tester) async {
final fakeOffices = _FakeUserOfficesController();
await tester.pumpWidget(
buildApp(
overrides: [
currentProfileProvider.overrideWith((ref) => Stream.value(profile)),
officesProvider.overrideWith(
(ref) => Stream.value([officeA, officeB]),
),
userOfficesProvider.overrideWith(
(ref) => Stream.value(const <UserOffice>[]),
),
userOfficesControllerProvider.overrideWithValue(fakeOffices),
],
),
);
await tester.pumpAndSettle();
// Open MultiSelect picker
final selectChip = find.byWidgetPredicate(
(w) => w is ActionChip && (w.label as Text).data == 'Select',
);
expect(selectChip, findsOneWidget);
await tester.ensureVisible(selectChip);
await tester.tap(selectChip);
await tester.pumpAndSettle();
// In the dialog, tap the first office checkbox and press Done
await tester.tap(find.text('HQ'));
await tester.pumpAndSettle();
await tester.tap(find.text('Done'));
await tester.pumpAndSettle();
// Save offices
await tester.tap(find.text('Save offices'));
await tester.pumpAndSettle();
expect(fakeOffices.assigned, isNotEmpty);
expect(fakeOffices.assigned.first['officeId'], equals('office-1'));
});
testWidgets('change password calls controller', (tester) async {
final fake = _FakeProfileController();
await tester.pumpWidget(
buildApp(
overrides: [
currentProfileProvider.overrideWith((ref) => Stream.value(profile)),
officesProvider.overrideWith((ref) => Stream.value([officeA])),
userOfficesProvider.overrideWith(
(ref) => Stream.value(const <UserOffice>[]),
),
profileControllerProvider.overrideWithValue(fake),
],
),
);
await tester.pumpAndSettle();
await tester.enterText(
find.widgetWithText(TextFormField, 'New password'),
'new-pass-123',
);
await tester.enterText(
find.widgetWithText(TextFormField, 'Confirm password'),
'new-pass-123',
);
await tester.ensureVisible(find.text('Change password'));
await tester.tap(find.text('Change password'));
await tester.pumpAndSettle();
expect(fake.lastPassword, equals('new-pass-123'));
});
}