added unit test

This commit is contained in:
verboomp
2026-01-30 14:30:53 +01:00
parent d9d64d2daa
commit 94c6becf9f
7 changed files with 145 additions and 101 deletions

View File

@@ -116,6 +116,8 @@ public class CustomerPictureService extends AbstractService {
criteriaQuery = criteriaQuery.where(builder.and(predicates.toArray(new Predicate[0]))); 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<Customer> typedQuery = entityManager.createQuery(criteriaQuery); TypedQuery<Customer> typedQuery = entityManager.createQuery(criteriaQuery);
List<Customer> customers = typedQuery.getResultList(); List<Customer> customers = typedQuery.getResultList();
customers.forEach(c -> c.getPictures().size()); customers.forEach(c -> c.getPictures().size());

View File

@@ -24,4 +24,9 @@ class PictureDto {
username: json['username'] as String? username: json['username'] as String?
); );
} }
@override
String toString() {
return 'PictureDto{id: $id}';
}
} }

View File

@@ -328,7 +328,7 @@ class _CustomerWidgetState extends State<CustomerWidget> {
} }
Future<void> _actionSelect(BuildContext context, CustomerDto customerDto, PictureDto pictureDto) async { Future<void> _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); context.go(uri);
setState(() { setState(() {
_dto = _customerController.get(id: widget.customerId); _dto = _customerController.get(id: widget.customerId);

View File

@@ -14,41 +14,55 @@ class PictureFullscreenDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { 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( return Dialog(
backgroundColor: _generalStyle.pageBackgroundColor, backgroundColor: Colors.black,
insetPadding: const EdgeInsets.all(24), clipBehavior: Clip.hardEdge,
child: ClipRRect( child: Column(
borderRadius: BorderRadius.circular(12), children: [
child: SizedBox( Padding(
width: MediaQuery.of(context).size.width * 0.9, padding: const EdgeInsets.only(top: 8.0, right: 8.0),
height: MediaQuery.of(context).size.height * 0.9, child: Row(
child: Stack( mainAxisAlignment: MainAxisAlignment.end,
fit: StackFit.expand, children: [
children: [ IconButton(
InteractiveViewer( style: IconButton.styleFrom(
panEnabled: true, backgroundColor: _generalStyle.primaryButtonBackgroundColor,
scaleEnabled: true, shape: const CircleBorder(),
minScale: 0.5, minimumSize: const Size(36, 36),
maxScale: 5.0, padding: const EdgeInsets.all(6),
child: Center(
child: Image.memory(
base64Decode(dto.image),
fit: BoxFit.contain,
), ),
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<void> _actionBack(BuildContext context) async {
Navigator.of(context).pop();
}
} }

View File

@@ -113,13 +113,17 @@ class _PictureWidgetState extends State<PictureWidget> {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final isNarrow = constraints.maxWidth < 800; final isNarrow = constraints.maxWidth < 800;
final maxImageWidth = constraints.maxWidth * 0.5;
return SingleChildScrollView( return SingleChildScrollView(
child: isNarrow child: isNarrow
? Column( ? Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_imageWidget(selectedPicture), ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxImageWidth),
child: _imageWidget(selectedPicture),
),
const SizedBox(height: 32), const SizedBox(height: 32),
_contentWidget(selectedPicture), _contentWidget(selectedPicture),
], ],
@@ -128,7 +132,10 @@ class _PictureWidgetState extends State<PictureWidget> {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_imageWidget(selectedPicture), ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxImageWidth),
child: _imageWidget(selectedPicture),
),
const SizedBox(width: 32), const SizedBox(width: 32),
Expanded(child: _contentWidget(selectedPicture)), Expanded(child: _contentWidget(selectedPicture)),
], ],
@@ -142,7 +149,7 @@ class _PictureWidgetState extends State<PictureWidget> {
return GestureDetector( return GestureDetector(
key: const Key("image"), key: const Key("image"),
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () => _showFullscreenImage(dto), onTap: () => _actionShowFullscreenImage(dto),
child: MouseRegion( child: MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: ConstrainedBox( child: ConstrainedBox(
@@ -156,15 +163,6 @@ class _PictureWidgetState extends State<PictureWidget> {
); );
} }
void _showFullscreenImage(PictureDto dto) {
showDialog(
context: context,
builder: (BuildContext context) {
return PictureFullscreenDialog(dto: dto);
},
);
}
Widget _contentWidget(PictureDto dto) { Widget _contentWidget(PictureDto dto) {
final labelStyle = TextStyle( final labelStyle = TextStyle(
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
@@ -211,9 +209,12 @@ class _PictureWidgetState extends State<PictureWidget> {
style: contentStyle, style: contentStyle,
), ),
), ),
Text( Padding(
AppLocalizations.of(context)!.pictureWidgetLabelZip, padding: const EdgeInsets.only(top: 24.0),
style: labelStyle, child: Text(
AppLocalizations.of(context)!.pictureWidgetLabelZip,
style: labelStyle,
),
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 4.0), padding: const EdgeInsets.only(top: 4.0),
@@ -222,9 +223,12 @@ class _PictureWidgetState extends State<PictureWidget> {
style: contentStyle, style: contentStyle,
), ),
), ),
Text( Padding(
AppLocalizations.of(context)!.pictureWidgetLabelCity, padding: const EdgeInsets.only(top: 24.0),
style: labelStyle, child: Text(
AppLocalizations.of(context)!.pictureWidgetLabelCity,
style: labelStyle,
),
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 4.0), padding: const EdgeInsets.only(top: 4.0),
@@ -233,32 +237,6 @@ class _PictureWidgetState extends State<PictureWidget> {
style: contentStyle, 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<PictureWidget> {
_evaluationCard(dto), _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<PictureWidget> {
onTap: () async => _actionUpdateEvaluation(value), onTap: () async => _actionUpdateEvaluation(value),
customBorder: const CircleBorder(), customBorder: const CircleBorder(),
child: Container( child: Container(
width: 32, width: 24,
height: 32, height: 24,
decoration: BoxDecoration( decoration: BoxDecoration(
color: color, color: color,
shape: BoxShape.circle, shape: BoxShape.circle,
@@ -345,10 +349,14 @@ class _PictureWidgetState extends State<PictureWidget> {
// Bottom navigation buttons // Bottom navigation buttons
Widget _bottomNavigationWidget(List<PictureDto> pictures, PictureDto selectedPicture) { Widget _bottomNavigationWidget(List<PictureDto> pictures, PictureDto selectedPicture) {
pictures.sort((a, b) => a.pictureDate.compareTo(b.pictureDate));
print(pictures);
final currentIndex = pictures.indexWhere((p) => p.id == selectedPicture.id); final currentIndex = pictures.indexWhere((p) => p.id == selectedPicture.id);
final hasPrevious = currentIndex > 0; final hasPrevious = currentIndex > 0;
final hasNext = currentIndex < pictures.length - 1; final hasNext = currentIndex < pictures.length - 1;
print("hasnext $hasNext hasPrevious $hasPrevious current $currentIndex");
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 24.0), padding: const EdgeInsets.only(bottom: 24.0),
child: Row( child: Row(
@@ -405,6 +413,15 @@ class _PictureWidgetState extends State<PictureWidget> {
); );
} }
void _actionShowFullscreenImage(PictureDto dto) {
showDialog(
context: context,
builder: (BuildContext context) {
return PictureFullscreenDialog(dto: dto);
},
);
}
Future<void> _actionUpdateEvaluation(int value) async { Future<void> _actionUpdateEvaluation(int value) async {
_selectedPicture?.evaluation = value; _selectedPicture?.evaluation = value;
_pictureController.updateEvaluation(_selectedPicture!); _pictureController.updateEvaluation(_selectedPicture!);
@@ -412,11 +429,14 @@ class _PictureWidgetState extends State<PictureWidget> {
} }
void _actionNavigateToPicture(int index) { void _actionNavigateToPicture(int index) {
print("navigate. too $index");
final pictures = _customerDto.pictures; final pictures = _customerDto.pictures;
if (index >= 0 && index < pictures.length) { if (index >= 0 && index < pictures.length) {
setState(() { setState(() {
_selectedPicture = pictures[index]; _selectedPicture = pictures[index];
}); });
}else {
print("empty");
} }
} }
} }

View File

@@ -40,23 +40,25 @@ class GlobalRouter {
builder: (context, state) => CustomerListWidget(), builder: (context, state) => CustomerListWidget(),
routes: [ routes: [
GoRoute( GoRoute(
path: "$pathCustomer/:id", path: "$pathCustomer/:customerId",
builder: (context, state) { builder: (context, state) {
var idStr = state.pathParameters['id']; var idStr = state.pathParameters['customerId'];
var id = idStr == null ? null : int.tryParse(idStr); var id = idStr == null ? null : int.tryParse(idStr);
return CustomerWidget(customerId: id ?? -1); return CustomerWidget(customerId: id ?? -1);
}, },
), routes: [
GoRoute( GoRoute(
path: "$pathPicture/:customerId/:pictureId", path: "$pathPicture/:pictureId",
builder: (context, state) { builder: (context, state) {
var customerIdStr = state.pathParameters['customerId']; var customerIdStr = state.pathParameters['customerId'];
var customerId = customerIdStr == null ? null : int.tryParse(customerIdStr); var customerId = customerIdStr == null ? null : int.tryParse(customerIdStr);
var pictureIdStr = state.pathParameters['pictureId']; var pictureIdStr = state.pathParameters['pictureId'];
var pictureId = pictureIdStr == null ? null : int.tryParse(pictureIdStr); var pictureId = pictureIdStr == null ? null : int.tryParse(pictureIdStr);
return PictureWidget(customerId: customerId ?? -1, pictureId: pictureId ?? -1); return PictureWidget(customerId: customerId ?? -1, pictureId: pictureId ?? -1);
}, },
),
],
), ),
], ],
), ),

View File

@@ -75,7 +75,7 @@ void main() {
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// Verify customer name is displayed // Verify customer name is displayed
@@ -95,7 +95,7 @@ void main() {
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// Verify the comment is displayed // Verify the comment is displayed
@@ -108,7 +108,7 @@ void main() {
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// The comment field should be empty but the label should exist // 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.get(id: 1)).thenAnswer((_) async => customerDto);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// Both navigation buttons should always be visible at the bottom // 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.get(id: 1)).thenAnswer((_) async => customerDto);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// Both navigation buttons should be visible // Both navigation buttons should be visible
@@ -149,7 +149,7 @@ void main() {
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// Both navigation buttons should be visible (right one is disabled) // 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.get(id: 1)).thenAnswer((_) async => customerDto);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// Verify first picture comment is shown // Verify first picture comment is shown
expect(find.text('First picture comment'), findsOneWidget); expect(find.text('First picture comment'), findsOneWidget);
// Tap right navigation button // 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(); await tester.pumpAndSettle();
// Verify second picture comment is now shown // Verify second picture comment is now shown
expect(find.text('Second picture comment'), findsOneWidget); expect(find.text('Second picture comment'), findsOneWidget);
expect(find.text('First picture comment'), findsNothing); expect(find.text('First picture comment'), findsNothing);
@@ -184,7 +185,7 @@ void main() {
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// Verify second picture comment is shown // Verify second picture comment is shown
@@ -212,7 +213,7 @@ void main() {
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => singlePictureCustomer); when(mockCustomerController.get(id: 1)).thenAnswer((_) async => singlePictureCustomer);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// Both navigation buttons should be shown but disabled // Both navigation buttons should be shown but disabled
@@ -226,7 +227,7 @@ void main() {
when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto); when(mockCustomerController.get(id: 1)).thenAnswer((_) async => customerDto);
when(mockCustomerController.getAll("", "")).thenAnswer((_) async => _list); 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(); await tester.pumpAndSettle();
// Find the GestureDetector with the image key // Find the GestureDetector with the image key