352 lines
12 KiB
Dart
352 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|||
|
|
import 'package:flutter/services.dart';
|
||
|
|
import 'package:flutter_test/flutter_test.dart';
|
||
|
|
import 'package:hidalyx_drluiscastro/core/widgets/medical_modal.dart';
|
||
|
|
import 'package:hidalyx_drluiscastro/features/config/data/services/config_service.dart';
|
||
|
|
import 'package:hidalyx_drluiscastro/features/config/presentation/store/config_store.dart';
|
||
|
|
import 'package:hidalyx_drluiscastro/features/config/presentation/views/config_modal_view.dart';
|
||
|
|
import 'package:hidalyx_drluiscastro/features/dashboard/data/models/doctor_info_model.dart';
|
||
|
|
|
||
|
|
class MockConfigService extends ConfigService {
|
||
|
|
DoctorInfoModel currentConfig;
|
||
|
|
bool shouldThrowOnSave = false;
|
||
|
|
bool shouldThrowOnLoad = false;
|
||
|
|
|
||
|
|
MockConfigService({
|
||
|
|
DoctorInfoModel? initialConfig,
|
||
|
|
}) : currentConfig = initialConfig ??
|
||
|
|
const DoctorInfoModel(
|
||
|
|
id: 1,
|
||
|
|
nombreDoctor: 'Dr. Luis A. Castro Sosa',
|
||
|
|
especialidad: 'Cirujano Urólogo',
|
||
|
|
descripEspecialidad: 'Enfermedades Vías Urinarias, Riñones',
|
||
|
|
descripGeneral: 'Santiago, R.D. Tel: 809-583-2394',
|
||
|
|
);
|
||
|
|
|
||
|
|
@override
|
||
|
|
Future<DoctorInfoModel> getConfig() async {
|
||
|
|
if (shouldThrowOnLoad) {
|
||
|
|
throw Exception('Error al cargar configuración de prueba');
|
||
|
|
}
|
||
|
|
return currentConfig;
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Future<DoctorInfoModel> updateConfig(DoctorInfoModel model) async {
|
||
|
|
if (shouldThrowOnSave) {
|
||
|
|
throw Exception('Error al guardar configuración de prueba');
|
||
|
|
}
|
||
|
|
currentConfig = model.copyWith(id: 1);
|
||
|
|
return currentConfig;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void main() {
|
||
|
|
group('DoctorInfoModel Serialization & Immutability', () {
|
||
|
|
test('serializes and deserializes properly from Directus format', () {
|
||
|
|
final json = {
|
||
|
|
'id': 1,
|
||
|
|
'nombre_doctor': 'Dr. Luis A. Castro Sosa',
|
||
|
|
'especialidad': 'Cirujano Urólogo',
|
||
|
|
'descrip_especialidad': 'Urología Oncológica y Laparoscopía',
|
||
|
|
'descrip_general': 'Consultorio 204, Santiago. Tel: 809-555-0100',
|
||
|
|
};
|
||
|
|
|
||
|
|
final model = DoctorInfoModel.fromJson(json);
|
||
|
|
|
||
|
|
expect(model.id, equals(1));
|
||
|
|
expect(model.nombreDoctor, equals('Dr. Luis A. Castro Sosa'));
|
||
|
|
expect(model.especialidad, equals('Cirujano Urólogo'));
|
||
|
|
expect(model.descripEspecialidad, equals('Urología Oncológica y Laparoscopía'));
|
||
|
|
expect(model.descripGeneral, equals('Consultorio 204, Santiago. Tel: 809-555-0100'));
|
||
|
|
|
||
|
|
// toJson without ID for Directus PATCH singleton
|
||
|
|
final jsonWithoutId = model.toJson();
|
||
|
|
expect(jsonWithoutId.containsKey('id'), isFalse);
|
||
|
|
expect(jsonWithoutId['nombre_doctor'], equals('Dr. Luis A. Castro Sosa'));
|
||
|
|
expect(jsonWithoutId['especialidad'], equals('Cirujano Urólogo'));
|
||
|
|
|
||
|
|
// toJson with ID
|
||
|
|
final jsonWithId = model.toJson(includeId: true);
|
||
|
|
expect(jsonWithId['id'], equals(1));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('copyWith properly modifies target fields while preserving others', () {
|
||
|
|
const original = DoctorInfoModel(
|
||
|
|
id: 1,
|
||
|
|
nombreDoctor: 'Dr. Luis Castro',
|
||
|
|
especialidad: 'Urólogo',
|
||
|
|
descripEspecialidad: 'General',
|
||
|
|
descripGeneral: 'Santiago',
|
||
|
|
);
|
||
|
|
|
||
|
|
final updated = original.copyWith(
|
||
|
|
especialidad: 'Urología Robótica',
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(updated.id, equals(1));
|
||
|
|
expect(updated.nombreDoctor, equals('Dr. Luis Castro'));
|
||
|
|
expect(updated.especialidad, equals('Urología Robótica'));
|
||
|
|
expect(updated.descripEspecialidad, equals('General'));
|
||
|
|
expect(updated.descripGeneral, equals('Santiago'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
group('ConfigStore State Management & Validation', () {
|
||
|
|
test('validation requires nombreDoctor and especialidad', () {
|
||
|
|
final mockService = MockConfigService();
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
|
||
|
|
expect(store.isValid, isFalse);
|
||
|
|
|
||
|
|
store.nombreDoctor = 'Dr. Luis Castro';
|
||
|
|
expect(store.isValid, isFalse);
|
||
|
|
|
||
|
|
store.especialidad = 'Cirujano Urólogo';
|
||
|
|
expect(store.isValid, isTrue);
|
||
|
|
|
||
|
|
store.nombreDoctor = ' ';
|
||
|
|
expect(store.isValid, isFalse);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('loadConfig successfully loads data into store observables', () async {
|
||
|
|
final mockService = MockConfigService();
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
|
||
|
|
expect(store.isLoading, isFalse);
|
||
|
|
final loadFuture = store.loadConfig();
|
||
|
|
expect(store.isLoading, isTrue);
|
||
|
|
await loadFuture;
|
||
|
|
|
||
|
|
expect(store.isLoading, isFalse);
|
||
|
|
expect(store.id, equals(1));
|
||
|
|
expect(store.nombreDoctor, equals('Dr. Luis A. Castro Sosa'));
|
||
|
|
expect(store.especialidad, equals('Cirujano Urólogo'));
|
||
|
|
expect(store.errorMessage, isNull);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('loadConfig sets errorMessage on failure', () async {
|
||
|
|
final mockService = MockConfigService()..shouldThrowOnLoad = true;
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
|
||
|
|
await store.loadConfig();
|
||
|
|
|
||
|
|
expect(store.isLoading, isFalse);
|
||
|
|
expect(store.errorMessage, isNotNull);
|
||
|
|
expect(store.errorMessage, contains('Error al cargar configuración'));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('saveConfig validates and updates via service', () async {
|
||
|
|
final mockService = MockConfigService();
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
|
||
|
|
// Attempt save with invalid state
|
||
|
|
final invalidResult = await store.saveConfig();
|
||
|
|
expect(invalidResult, isNull);
|
||
|
|
expect(store.errorMessage, isNotNull);
|
||
|
|
|
||
|
|
// Set valid state and save
|
||
|
|
store.nombreDoctor = 'Dr. Juan Perez';
|
||
|
|
store.especialidad = 'Cardiólogo';
|
||
|
|
store.descripEspecialidad = 'Arritmias y Marcapasos';
|
||
|
|
store.descripGeneral = 'Clínica Corominas';
|
||
|
|
|
||
|
|
final savedResult = await store.saveConfig();
|
||
|
|
expect(savedResult, isNotNull);
|
||
|
|
expect(savedResult!.nombreDoctor, equals('Dr. Juan Perez'));
|
||
|
|
expect(savedResult.especialidad, equals('Cardiólogo'));
|
||
|
|
expect(store.successMessage, isNotNull);
|
||
|
|
expect(store.isSaving, isFalse);
|
||
|
|
expect(store.errorMessage, isNull);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('saveConfig handles service error gracefully', () async {
|
||
|
|
final mockService = MockConfigService()..shouldThrowOnSave = true;
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
|
||
|
|
store.nombreDoctor = 'Dr. Luis Castro';
|
||
|
|
store.especialidad = 'Urología';
|
||
|
|
|
||
|
|
final result = await store.saveConfig();
|
||
|
|
expect(result, isNull);
|
||
|
|
expect(store.isSaving, isFalse);
|
||
|
|
expect(store.errorMessage, contains('Error al guardar configuración'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
group('ConfigModalView Widget Tests', () {
|
||
|
|
testWidgets('renders all fields and sections properly', (tester) async {
|
||
|
|
final mockService = MockConfigService();
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
await store.loadConfig();
|
||
|
|
|
||
|
|
await tester.pumpWidget(
|
||
|
|
MaterialApp(
|
||
|
|
home: Scaffold(
|
||
|
|
body: ConfigModalView(
|
||
|
|
store: store,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
// Check titles & subtitles
|
||
|
|
expect(find.text('Configuración del Consultorio'), findsOneWidget);
|
||
|
|
expect(find.text('Identidad del Profesional'), findsOneWidget);
|
||
|
|
expect(find.text('Áreas de Enfoque y Subespecialidades'), findsOneWidget);
|
||
|
|
expect(find.text('Datos del Consultorio y Contacto'), findsOneWidget);
|
||
|
|
|
||
|
|
// Check buttons and footer elements
|
||
|
|
expect(find.text('Cancelar'), findsOneWidget);
|
||
|
|
expect(find.text('Guardar Configuración'), findsOneWidget);
|
||
|
|
expect(find.text('ESC para salir'), findsOneWidget);
|
||
|
|
|
||
|
|
// Check populated initial values
|
||
|
|
expect(find.text('Dr. Luis A. Castro Sosa'), findsOneWidget);
|
||
|
|
expect(find.text('Cirujano Urólogo'), findsOneWidget);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('footer container has established padding separating buttons from extremities', (tester) async {
|
||
|
|
final mockService = MockConfigService();
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
await store.loadConfig();
|
||
|
|
|
||
|
|
await tester.pumpWidget(
|
||
|
|
MaterialApp(
|
||
|
|
home: Scaffold(
|
||
|
|
body: ConfigModalView(
|
||
|
|
store: store,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
// Find the footer Container that holds the buttons and ESC hint
|
||
|
|
final footerFinder = find.ancestor(
|
||
|
|
of: find.text('Guardar Configuración'),
|
||
|
|
matching: find.byType(Container),
|
||
|
|
);
|
||
|
|
expect(footerFinder, findsWidgets);
|
||
|
|
|
||
|
|
final footerContainer = tester.widget<Container>(footerFinder.first);
|
||
|
|
expect(footerContainer.padding, equals(const EdgeInsets.symmetric(horizontal: 20, vertical: 12)));
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('cancels and closes when Cancelar button is pressed', (tester) async {
|
||
|
|
final mockService = MockConfigService();
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
await store.loadConfig();
|
||
|
|
|
||
|
|
await tester.pumpWidget(
|
||
|
|
MaterialApp(
|
||
|
|
home: Scaffold(
|
||
|
|
body: Builder(
|
||
|
|
builder: (ctx) => ElevatedButton(
|
||
|
|
onPressed: () => showMedicalModal(
|
||
|
|
context: ctx,
|
||
|
|
builder: (_) => ConfigModalView(store: store),
|
||
|
|
),
|
||
|
|
child: const Text('Abrir Modal'),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
// Open modal
|
||
|
|
await tester.tap(find.text('Abrir Modal'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
expect(find.text('Configuración del Consultorio'), findsOneWidget);
|
||
|
|
|
||
|
|
// Tap Cancelar
|
||
|
|
await tester.tap(find.text('Cancelar'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
// Modal is dismissed
|
||
|
|
expect(find.text('Configuración del Consultorio'), findsNothing);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('closes modal when ESC key is pressed', (tester) async {
|
||
|
|
final mockService = MockConfigService();
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
await store.loadConfig();
|
||
|
|
|
||
|
|
await tester.pumpWidget(
|
||
|
|
MaterialApp(
|
||
|
|
home: Scaffold(
|
||
|
|
body: Builder(
|
||
|
|
builder: (ctx) => ElevatedButton(
|
||
|
|
onPressed: () => showMedicalModal(
|
||
|
|
context: ctx,
|
||
|
|
builder: (_) => ConfigModalView(store: store),
|
||
|
|
),
|
||
|
|
child: const Text('Abrir Modal'),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
// Open modal
|
||
|
|
await tester.tap(find.text('Abrir Modal'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
expect(find.text('Configuración del Consultorio'), findsOneWidget);
|
||
|
|
|
||
|
|
// Simulate ESC key
|
||
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
// Modal must be dismissed via ESC
|
||
|
|
expect(find.text('Configuración del Consultorio'), findsNothing);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('saves modified configuration and triggers onSaved callback', (tester) async {
|
||
|
|
final mockService = MockConfigService();
|
||
|
|
final store = ConfigStore(configService: mockService);
|
||
|
|
await store.loadConfig();
|
||
|
|
|
||
|
|
DoctorInfoModel? savedModel;
|
||
|
|
|
||
|
|
await tester.pumpWidget(
|
||
|
|
MaterialApp(
|
||
|
|
home: Scaffold(
|
||
|
|
body: Builder(
|
||
|
|
builder: (ctx) => ElevatedButton(
|
||
|
|
onPressed: () => showMedicalModal(
|
||
|
|
context: ctx,
|
||
|
|
builder: (_) => ConfigModalView(
|
||
|
|
store: store,
|
||
|
|
onSaved: (m) => savedModel = m,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
child: const Text('Abrir Modal'),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
// Open modal
|
||
|
|
await tester.tap(find.text('Abrir Modal'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
// Enter updated doctor name
|
||
|
|
final nameField = find.widgetWithText(TextField, 'Dr. Luis A. Castro Sosa');
|
||
|
|
await tester.enterText(nameField, 'Dr. Luis Alberto Castro');
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
// Tap Guardar
|
||
|
|
await tester.tap(find.text('Guardar Configuración'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
// Verify callback was called with updated name and modal closed
|
||
|
|
expect(savedModel, isNotNull);
|
||
|
|
expect(savedModel!.nombreDoctor, equals('Dr. Luis Alberto Castro'));
|
||
|
|
expect(find.text('Configuración del Consultorio'), findsNothing);
|
||
|
|
expect(find.text('Configuración del consultorio guardada correctamente'), findsOneWidget);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|