feat(patient): implement doctor-only patient deletion, alert theme styling, and test auto-cleanup
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hidalyx_drluiscastro/features/patient/data/models/patient_model.dart';
|
||||
import 'package:hidalyx_drluiscastro/features/patient/data/services/patient_service.dart';
|
||||
import 'package:hidalyx_drluiscastro/features/patient/presentation/store/patient_store.dart';
|
||||
import 'package:hidalyx_drluiscastro/features/patient/presentation/views/patient_modal_view.dart';
|
||||
|
||||
class MockPatientServiceForDelete extends PatientService {
|
||||
int? deletedPatientId;
|
||||
bool shouldFail = false;
|
||||
|
||||
@override
|
||||
Future<void> deletePatient(int id) async {
|
||||
if (shouldFail) {
|
||||
throw Exception('Error simulado al eliminar paciente');
|
||||
}
|
||||
deletedPatientId = id;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Patient Deletion Unit & Store Tests', () {
|
||||
test('PatientStore.deletePatient deletes patient and updates state', () async {
|
||||
final mockService = MockPatientServiceForDelete();
|
||||
final store = PatientStore(patientService: mockService);
|
||||
|
||||
// Attempt to delete with null id
|
||||
final nullResult = await store.deletePatient();
|
||||
expect(nullResult, isFalse);
|
||||
expect(store.errorMessage, contains('No se puede eliminar un paciente que no ha sido guardado'));
|
||||
|
||||
// Populate patient
|
||||
store.populateFromModel(const PatientModel(
|
||||
id: 777,
|
||||
nombre: 'CARLOS',
|
||||
apellido: 'MARTINEZ',
|
||||
));
|
||||
expect(store.id, 777);
|
||||
|
||||
// Execute deletion
|
||||
final success = await store.deletePatient();
|
||||
expect(success, isTrue);
|
||||
expect(mockService.deletedPatientId, 777);
|
||||
expect(store.isDeleting, isFalse);
|
||||
expect(store.successMessage, 'Paciente eliminado exitosamente');
|
||||
expect(store.errorMessage, isNull);
|
||||
});
|
||||
|
||||
test('PatientStore.deletePatient handles service errors gracefully', () async {
|
||||
final mockService = MockPatientServiceForDelete()..shouldFail = true;
|
||||
final store = PatientStore(patientService: mockService);
|
||||
|
||||
store.populateFromModel(const PatientModel(
|
||||
id: 888,
|
||||
nombre: 'LUCIA',
|
||||
apellido: 'MENDEZ',
|
||||
));
|
||||
|
||||
final success = await store.deletePatient();
|
||||
expect(success, isFalse);
|
||||
expect(store.isDeleting, isFalse);
|
||||
expect(store.errorMessage, contains('Error simulado al eliminar paciente'));
|
||||
});
|
||||
});
|
||||
|
||||
group('Patient Deletion UI & Role Tests', () {
|
||||
testWidgets('Doctor sees "Eliminar" button when editing an existing patient', (tester) async {
|
||||
tester.view.physicalSize = const Size(1400, 1000);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
const patient = PatientModel(
|
||||
id: 555,
|
||||
nombre: 'JUAN',
|
||||
apellido: 'PEREZ',
|
||||
cedula: '001-9999999-1',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: PatientModalView(
|
||||
initialPatient: patient,
|
||||
isSecretary: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Eliminar'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Doctor does NOT see "Eliminar" button when creating a new patient', (tester) async {
|
||||
tester.view.physicalSize = const Size(1400, 1000);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: PatientModalView(
|
||||
isSecretary: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Eliminar'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Secretary NEVER sees "Eliminar" button even when editing an existing patient', (tester) async {
|
||||
tester.view.physicalSize = const Size(1400, 1000);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
const patient = PatientModel(
|
||||
id: 555,
|
||||
nombre: 'JUAN',
|
||||
apellido: 'PEREZ',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: PatientModalView(
|
||||
initialPatient: patient,
|
||||
isSecretary: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Eliminar'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Clicking "Eliminar" opens confirmation dialog, Cancel does not delete', (tester) async {
|
||||
tester.view.physicalSize = const Size(1400, 1000);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
final mockService = MockPatientServiceForDelete();
|
||||
final store = PatientStore(patientService: mockService);
|
||||
store.populateFromModel(const PatientModel(
|
||||
id: 123,
|
||||
nombre: 'ROBERTO',
|
||||
apellido: 'GARCIA',
|
||||
cedula: '402-1234567-8',
|
||||
));
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: PatientModalView(
|
||||
store: store,
|
||||
isSecretary: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Click Eliminar button
|
||||
await tester.tap(find.text('Eliminar'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify confirmation dialog
|
||||
expect(find.text('¿Eliminar Ficha del Paciente?'), findsOneWidget);
|
||||
expect(find.text('ROBERTO GARCIA'), findsOneWidget);
|
||||
expect(find.textContaining('#123'), findsOneWidget);
|
||||
expect(find.textContaining('Esta acción es permanente e irreversible'), findsOneWidget);
|
||||
|
||||
// Click Cancelar
|
||||
await tester.tap(find.text('Cancelar'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Dialog dismissed, patient NOT deleted
|
||||
expect(find.text('¿Eliminar Ficha del Paciente?'), findsNothing);
|
||||
expect(mockService.deletedPatientId, isNull);
|
||||
});
|
||||
|
||||
testWidgets('Confirming deletion invokes service, calls onDeleted, and closes modal', (tester) async {
|
||||
tester.view.physicalSize = const Size(1400, 1000);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() => tester.view.resetPhysicalSize());
|
||||
|
||||
final mockService = MockPatientServiceForDelete();
|
||||
final store = PatientStore(patientService: mockService);
|
||||
store.populateFromModel(const PatientModel(
|
||||
id: 999,
|
||||
nombre: 'ALBERTO',
|
||||
apellido: 'MOREL',
|
||||
));
|
||||
|
||||
int? deletedIdReceived;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (ctx) => ElevatedButton(
|
||||
onPressed: () => showDialog(
|
||||
context: ctx,
|
||||
builder: (_) => PatientModalView(
|
||||
store: store,
|
||||
isSecretary: false,
|
||||
onDeleted: (id) => deletedIdReceived = id,
|
||||
),
|
||||
),
|
||||
child: const Text('Open Modal'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Open modal
|
||||
await tester.tap(find.text('Open Modal'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Click Eliminar
|
||||
await tester.tap(find.text('Eliminar'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Click Eliminar Paciente inside confirmation dialog
|
||||
await tester.tap(find.text('Eliminar Paciente'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(mockService.deletedPatientId, 999);
|
||||
expect(deletedIdReceived, 999);
|
||||
expect(find.textContaining('eliminada exitosamente'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user