From 94c6becf9f6c5f10cac945f8533523c5f2b70915 Mon Sep 17 00:00:00 2001 From: verboomp Date: Fri, 30 Jan 2026 14:30:53 +0100 Subject: [PATCH] added unit test --- .../core/service/CustomerPictureService.java | 2 + .../lib/dto/picture_dto.dart | 5 + .../lib/pages/customer/customer_widget.dart | 2 +- .../customer/picture_fullscreen_dialog.dart | 74 +++++++----- .../lib/pages/customer/picture_widget.dart | 112 +++++++++++------- .../lib/utils/global_router.dart | 26 ++-- .../test/pages/picture_widget_test.dart | 25 ++-- 7 files changed, 145 insertions(+), 101 deletions(-) diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/CustomerPictureService.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/CustomerPictureService.java index 8eca020..2d9a2af 100644 --- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/CustomerPictureService.java +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/CustomerPictureService.java @@ -116,6 +116,8 @@ public class CustomerPictureService extends AbstractService { criteriaQuery = criteriaQuery.where(builder.and(predicates.toArray(new Predicate[0]))); } + //criteriaQuery = criteriaQuery.orderBy(builder.asc(builder.lower(customerRoot.get("name")))); FIXME: this causes errors + TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); List customers = typedQuery.getResultList(); customers.forEach(c -> c.getPictures().size()); diff --git a/hartmann-foto-documentation-frontend/lib/dto/picture_dto.dart b/hartmann-foto-documentation-frontend/lib/dto/picture_dto.dart index 8ca93be..3a7a3fa 100644 --- a/hartmann-foto-documentation-frontend/lib/dto/picture_dto.dart +++ b/hartmann-foto-documentation-frontend/lib/dto/picture_dto.dart @@ -24,4 +24,9 @@ class PictureDto { username: json['username'] as String? ); } + + @override + String toString() { + return 'PictureDto{id: $id}'; + } } \ No newline at end of file diff --git a/hartmann-foto-documentation-frontend/lib/pages/customer/customer_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/customer/customer_widget.dart index 2dc1cfd..fdd29e3 100644 --- a/hartmann-foto-documentation-frontend/lib/pages/customer/customer_widget.dart +++ b/hartmann-foto-documentation-frontend/lib/pages/customer/customer_widget.dart @@ -328,7 +328,7 @@ class _CustomerWidgetState extends State { } Future _actionSelect(BuildContext context, CustomerDto customerDto, PictureDto pictureDto) async { - String uri = "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/${customerDto.id}/${pictureDto.id}"; + String uri = "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/${customerDto.id}${GlobalRouter.pathPicture}/${pictureDto.id}"; context.go(uri); setState(() { _dto = _customerController.get(id: widget.customerId); diff --git a/hartmann-foto-documentation-frontend/lib/pages/customer/picture_fullscreen_dialog.dart b/hartmann-foto-documentation-frontend/lib/pages/customer/picture_fullscreen_dialog.dart index 72a4b43..2d04eba 100644 --- a/hartmann-foto-documentation-frontend/lib/pages/customer/picture_fullscreen_dialog.dart +++ b/hartmann-foto-documentation-frontend/lib/pages/customer/picture_fullscreen_dialog.dart @@ -14,41 +14,55 @@ class PictureFullscreenDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final screenSize = MediaQuery.of(context).size; + final dialogWidth = screenSize.width * 0.9; + final dialogHeight = screenSize.height * 0.9 - 50; + + final imageBytes = base64Decode(dto.image); return Dialog( - backgroundColor: _generalStyle.pageBackgroundColor, - insetPadding: const EdgeInsets.all(24), - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.9, - height: MediaQuery.of(context).size.height * 0.9, - child: Stack( - fit: StackFit.expand, - children: [ - InteractiveViewer( - panEnabled: true, - scaleEnabled: true, - minScale: 0.5, - maxScale: 5.0, - child: Center( - child: Image.memory( - base64Decode(dto.image), - fit: BoxFit.contain, + backgroundColor: Colors.black, + clipBehavior: Clip.hardEdge, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 8.0, right: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + style: IconButton.styleFrom( + backgroundColor: _generalStyle.primaryButtonBackgroundColor, + shape: const CircleBorder(), + minimumSize: const Size(36, 36), + padding: const EdgeInsets.all(6), ), + icon: const Icon(Icons.close, color: Colors.white, size: 20), + onPressed: () async => await _actionBack(context), ), - ), - Positioned( - top: 16, - right: 16, - child: IconButton( - icon: Icon(Icons.close, color: _generalStyle.primaryButtonBackgroundColor, size: 32), - onPressed: () => Navigator.of(context).pop(), - ), - ), - ], + ], + ), ), - ), + SizedBox( + width: dialogWidth, + height: dialogHeight, + child: InteractiveViewer( + constrained: true, + //boundaryMargin: EdgeInsets.all(20), + panEnabled: true, + scaleEnabled: true, + minScale: 0.5, + maxScale: 5.0, + child: Image.memory( + imageBytes, + ), + ), + ), + ], ), ); } + + Future _actionBack(BuildContext context) async { + Navigator.of(context).pop(); + } } diff --git a/hartmann-foto-documentation-frontend/lib/pages/customer/picture_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/customer/picture_widget.dart index ad0bd66..b3e68d8 100644 --- a/hartmann-foto-documentation-frontend/lib/pages/customer/picture_widget.dart +++ b/hartmann-foto-documentation-frontend/lib/pages/customer/picture_widget.dart @@ -113,13 +113,17 @@ class _PictureWidgetState extends State { return LayoutBuilder( builder: (context, constraints) { final isNarrow = constraints.maxWidth < 800; + final maxImageWidth = constraints.maxWidth * 0.5; return SingleChildScrollView( child: isNarrow ? Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _imageWidget(selectedPicture), + ConstrainedBox( + constraints: BoxConstraints(maxWidth: maxImageWidth), + child: _imageWidget(selectedPicture), + ), const SizedBox(height: 32), _contentWidget(selectedPicture), ], @@ -128,7 +132,10 @@ class _PictureWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _imageWidget(selectedPicture), + ConstrainedBox( + constraints: BoxConstraints(maxWidth: maxImageWidth), + child: _imageWidget(selectedPicture), + ), const SizedBox(width: 32), Expanded(child: _contentWidget(selectedPicture)), ], @@ -142,7 +149,7 @@ class _PictureWidgetState extends State { return GestureDetector( key: const Key("image"), behavior: HitTestBehavior.opaque, - onTap: () => _showFullscreenImage(dto), + onTap: () => _actionShowFullscreenImage(dto), child: MouseRegion( cursor: SystemMouseCursors.click, child: ConstrainedBox( @@ -156,15 +163,6 @@ class _PictureWidgetState extends State { ); } - void _showFullscreenImage(PictureDto dto) { - showDialog( - context: context, - builder: (BuildContext context) { - return PictureFullscreenDialog(dto: dto); - }, - ); - } - Widget _contentWidget(PictureDto dto) { final labelStyle = TextStyle( fontWeight: FontWeight.normal, @@ -211,9 +209,12 @@ class _PictureWidgetState extends State { style: contentStyle, ), ), - Text( - AppLocalizations.of(context)!.pictureWidgetLabelZip, - style: labelStyle, + Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Text( + AppLocalizations.of(context)!.pictureWidgetLabelZip, + style: labelStyle, + ), ), Padding( padding: const EdgeInsets.only(top: 4.0), @@ -222,9 +223,12 @@ class _PictureWidgetState extends State { style: contentStyle, ), ), - Text( - AppLocalizations.of(context)!.pictureWidgetLabelCity, - style: labelStyle, + Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Text( + AppLocalizations.of(context)!.pictureWidgetLabelCity, + style: labelStyle, + ), ), Padding( padding: const EdgeInsets.only(top: 4.0), @@ -233,32 +237,6 @@ class _PictureWidgetState extends State { style: contentStyle, ), ), - Padding( - padding: const EdgeInsets.only(top: 20.0), - child: Text( - AppLocalizations.of(context)!.pictureWidgetLabelComment, - style: labelStyle, - ), - ), - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: SizedBox( - width: double.infinity, - height: 150, - child: Scrollbar( - controller: _commentScrollController, - thumbVisibility: true, - child: SingleChildScrollView( - controller: _commentScrollController, - padding: const EdgeInsets.all(8.0), - child: Text( - dto.comment ?? "", - style: contentStyle, - ), - ), - ), - ), - ), ], ), ), @@ -266,6 +244,32 @@ class _PictureWidgetState extends State { _evaluationCard(dto), ], ), + Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Text( + AppLocalizations.of(context)!.pictureWidgetLabelComment, + style: labelStyle, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: SizedBox( + width: double.infinity, + height: 150, + child: Scrollbar( + controller: _commentScrollController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: _commentScrollController, + padding: const EdgeInsets.all(8.0), + child: Text( + dto.comment ?? "", + style: contentStyle, + ), + ), + ), + ), + ), ], ); } @@ -332,8 +336,8 @@ class _PictureWidgetState extends State { onTap: () async => _actionUpdateEvaluation(value), customBorder: const CircleBorder(), child: Container( - width: 32, - height: 32, + width: 24, + height: 24, decoration: BoxDecoration( color: color, shape: BoxShape.circle, @@ -345,10 +349,14 @@ class _PictureWidgetState extends State { // Bottom navigation buttons Widget _bottomNavigationWidget(List pictures, PictureDto selectedPicture) { + pictures.sort((a, b) => a.pictureDate.compareTo(b.pictureDate)); + print(pictures); final currentIndex = pictures.indexWhere((p) => p.id == selectedPicture.id); final hasPrevious = currentIndex > 0; final hasNext = currentIndex < pictures.length - 1; + print("hasnext $hasNext hasPrevious $hasPrevious current $currentIndex"); + return Padding( padding: const EdgeInsets.only(bottom: 24.0), child: Row( @@ -405,6 +413,15 @@ class _PictureWidgetState extends State { ); } + void _actionShowFullscreenImage(PictureDto dto) { + showDialog( + context: context, + builder: (BuildContext context) { + return PictureFullscreenDialog(dto: dto); + }, + ); + } + Future _actionUpdateEvaluation(int value) async { _selectedPicture?.evaluation = value; _pictureController.updateEvaluation(_selectedPicture!); @@ -412,11 +429,14 @@ class _PictureWidgetState extends State { } void _actionNavigateToPicture(int index) { + print("navigate. too $index"); final pictures = _customerDto.pictures; if (index >= 0 && index < pictures.length) { setState(() { _selectedPicture = pictures[index]; }); + }else { + print("empty"); } } } diff --git a/hartmann-foto-documentation-frontend/lib/utils/global_router.dart b/hartmann-foto-documentation-frontend/lib/utils/global_router.dart index 85d821b..9a9b277 100644 --- a/hartmann-foto-documentation-frontend/lib/utils/global_router.dart +++ b/hartmann-foto-documentation-frontend/lib/utils/global_router.dart @@ -40,23 +40,25 @@ class GlobalRouter { builder: (context, state) => CustomerListWidget(), routes: [ GoRoute( - path: "$pathCustomer/:id", + path: "$pathCustomer/:customerId", builder: (context, state) { - var idStr = state.pathParameters['id']; + var idStr = state.pathParameters['customerId']; var id = idStr == null ? null : int.tryParse(idStr); return CustomerWidget(customerId: id ?? -1); }, - ), - GoRoute( - path: "$pathPicture/:customerId/:pictureId", - builder: (context, state) { - var customerIdStr = state.pathParameters['customerId']; - var customerId = customerIdStr == null ? null : int.tryParse(customerIdStr); + routes: [ + GoRoute( + path: "$pathPicture/:pictureId", + builder: (context, state) { + var customerIdStr = state.pathParameters['customerId']; + var customerId = customerIdStr == null ? null : int.tryParse(customerIdStr); - var pictureIdStr = state.pathParameters['pictureId']; - var pictureId = pictureIdStr == null ? null : int.tryParse(pictureIdStr); - return PictureWidget(customerId: customerId ?? -1, pictureId: pictureId ?? -1); - }, + var pictureIdStr = state.pathParameters['pictureId']; + var pictureId = pictureIdStr == null ? null : int.tryParse(pictureIdStr); + return PictureWidget(customerId: customerId ?? -1, pictureId: pictureId ?? -1); + }, + ), + ], ), ], ), diff --git a/hartmann-foto-documentation-frontend/test/pages/picture_widget_test.dart b/hartmann-foto-documentation-frontend/test/pages/picture_widget_test.dart index 10f6cf3..4730919 100644 --- a/hartmann-foto-documentation-frontend/test/pages/picture_widget_test.dart +++ b/hartmann-foto-documentation-frontend/test/pages/picture_widget_test.dart @@ -75,7 +75,7 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/1"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await tester.pumpAndSettle(); // Verify customer name is displayed @@ -95,7 +95,7 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/1"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await tester.pumpAndSettle(); // Verify the comment is displayed @@ -108,7 +108,7 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/1"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await tester.pumpAndSettle(); // The comment field should be empty but the label should exist @@ -121,7 +121,7 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/1"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await tester.pumpAndSettle(); // Both navigation buttons should always be visible at the bottom @@ -135,7 +135,7 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/1"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await tester.pumpAndSettle(); // Both navigation buttons should be visible @@ -149,7 +149,7 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/1"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await tester.pumpAndSettle(); // Both navigation buttons should be visible (right one is disabled) @@ -163,16 +163,17 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/1"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await tester.pumpAndSettle(); // Verify first picture comment is shown expect(find.text('First picture comment'), findsOneWidget); // Tap right navigation button - await tester.tap(find.byIcon(Icons.chevron_right)); + print(find.byKey(Key("navigate_right"))); + await tester.tap(find.byKey(Key("navigate_right"))); await tester.pumpAndSettle(); - + // Verify second picture comment is now shown expect(find.text('Second picture comment'), findsOneWidget); expect(find.text('First picture comment'), findsNothing); @@ -184,7 +185,7 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/2"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/2"); await tester.pumpAndSettle(); // Verify second picture comment is shown @@ -212,7 +213,7 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => singlePictureCustomer); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/1"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await tester.pumpAndSettle(); // Both navigation buttons should be shown but disabled @@ -226,7 +227,7 @@ void main() { when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); - await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathPicture}/1/1"); + await pumpAppConfig(tester, "${GlobalRouter.pathHome}${GlobalRouter.pathCustomer}/1${GlobalRouter.pathPicture}/1"); await tester.pumpAndSettle(); // Find the GestureDetector with the image key