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

@@ -24,4 +24,9 @@ class PictureDto {
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 {
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);

View File

@@ -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<void> _actionBack(BuildContext context) async {
Navigator.of(context).pop();
}
}

View File

@@ -113,13 +113,17 @@ class _PictureWidgetState extends State<PictureWidget> {
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<PictureWidget> {
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<PictureWidget> {
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<PictureWidget> {
);
}
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<PictureWidget> {
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<PictureWidget> {
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<PictureWidget> {
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),
],
),
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),
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<PictureWidget> {
// Bottom navigation buttons
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 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<PictureWidget> {
);
}
void _actionShowFullscreenImage(PictureDto dto) {
showDialog(
context: context,
builder: (BuildContext context) {
return PictureFullscreenDialog(dto: dto);
},
);
}
Future<void> _actionUpdateEvaluation(int value) async {
_selectedPicture?.evaluation = value;
_pictureController.updateEvaluation(_selectedPicture!);
@@ -412,11 +429,14 @@ class _PictureWidgetState extends State<PictureWidget> {
}
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");
}
}
}

View File

@@ -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);
},
),
],
),
],
),

View File

@@ -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