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