test: add unit tests for poultry repository and searchable dropdown functionalities

- Introduced tests for `PoultryScienceRepositoryImp` to validate delegated remote calls.
- Added comprehensive tests for `SearchableDropdownLogic` covering selection, overlay, and search logic.
- Enhanced `SearchableDropdown` widget tests for multi-select, label building, and overlay management.
This commit is contained in:
2025-11-16 15:40:21 +03:30
parent 716a7ed259
commit a66c8b69ca
16 changed files with 812 additions and 304 deletions

View File

@@ -5,7 +5,7 @@ import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/utils/string_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/steward/inventory_widget.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_core/core.dart' hide modalDatePicker;
import 'logic.dart';
@@ -412,12 +412,14 @@ class SalesInProvincePage extends GetView<SalesInProvinceLogic> {
children: [
Expanded(
child: timeFilterWidget(
controller: controller,
date: controller.fromDateFilter,
onChanged: (jalali) => controller.fromDateFilter.value = jalali,
),
),
Expanded(
child: timeFilterWidget(
controller: controller,
isFrom: false,
date: controller.toDateFilter,
onChanged: (jalali) => controller.toDateFilter.value = jalali,
@@ -441,6 +443,7 @@ class SalesInProvincePage extends GetView<SalesInProvinceLogic> {
}
GestureDetector timeFilterWidget({
required SalesInProvinceLogic controller,
isFrom = true,
required Rx<Jalali> date,
required Function(Jalali jalali) onChanged,
@@ -482,139 +485,4 @@ class SalesInProvincePage extends GetView<SalesInProvinceLogic> {
),
);
}
Container modalDatePicker(ValueChanged<Jalali> onDateSelected) {
Jalali? tempPickedDate;
return Container(
height: 250,
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: Row(
children: [
SizedBox(width: 20),
RElevated(
height: 35,
width: 70,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'تایید',
),
Spacer(),
RElevated(
height: 35,
width: 70,
backgroundColor: AppColor.error,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'لغو',
),
SizedBox(width: 20),
],
),
),
Divider(height: 0, thickness: 1),
Expanded(
child: Container(
child: PersianCupertinoDatePicker(
initialDateTime: Jalali.now(),
mode: PersianCupertinoDatePickerMode.date,
onDateTimeChanged: (dateTime) {
tempPickedDate = dateTime;
},
),
),
),
],
),
);
}
Widget show2StepAddBottomSheet() {
return BaseBottomSheet(
height: Get.height * .39,
child: Column(
spacing: 8,
children: [
buildRow(
title: 'تاریخ ثبت',
value: controller.tmpStewardAllocation?.date?.formattedJalaliDate ?? 'ندارد',
),
buildRow(
title: 'نام و نام خانوادگی خریدار',
value:
controller.guildsModel
.firstWhere((p0) => p0.key == controller.tmpStewardAllocation?.guildKey)
.user
?.fullname ??
'ندارد',
),
buildRow(
title: 'شماره خریدار',
value:
controller.guildsModel
.firstWhere((p0) => p0.key == controller.tmpStewardAllocation?.guildKey)
.user
?.mobile ??
'ندارد',
),
buildRow(
title: 'قیمت هر کیلو',
value: '${controller.tmpStewardAllocation?.amount.separatedByCommaFa ?? 0} ریال ',
),
buildRow(
title: 'وزن تخصیصی',
value:
'${controller.tmpStewardAllocation?.weightOfCarcasses?.toInt().separatedByCommaFa ?? 0} کیلوگرم',
),
buildRow(
title: 'قیمت کل',
value: '${controller.tmpStewardAllocation?.totalAmount.separatedByCommaFa ?? 0} ریال',
),
Row(
spacing: 10,
children: [
Expanded(
child: RElevated(
backgroundColor: AppColor.greenNormal,
height: 40,
text: 'ثبت',
textStyle: AppFonts.yekan18.copyWith(color: Colors.white),
onPressed: () async {
await controller.submitAllocation();
Get
..back()
..back();
},
),
),
Expanded(
child: ROutlinedElevated(
height: 40,
borderColor: AppColor.error,
text: ' بازگشت',
textStyle: AppFonts.yekan18.copyWith(color: AppColor.error),
onPressed: () {
Get
..back()
..back();
},
),
),
],
),
],
),
);
}
}

View File

@@ -31,15 +31,24 @@ Widget addOrEditBottomSheet(SalesInProvinceLogic controller, {bool isEditMode =
spacing: 12,
children: [
const SizedBox(height: 8),
RTextField(
controller: TextEditingController(),
filledColor: AppColor.bgLight,
label: 'تاریخ',
readonly: true,
borderColor: AppColor.darkGreyLight,
initText: Jalali.now().formatCompactDate(),
),
ObxValue((data) {
return RTextField(
controller: TextEditingController(),
filledColor: AppColor.bgLight,
filled: true,
label: 'تاریخ',
onTap: () {
Get.bottomSheet(
modalDatePicker((value) {
controller.fromDateFilter.value = value;
controller.fromDateFilter.refresh();
}),
);
},
borderColor: AppColor.darkGreyLight,
initText: (data.value ?? Jalali.now()).formatCompactDate(),
);
}, controller.fromDateFilter),
Visibility(
visible: isEditMode == false,
child: Container(
@@ -271,9 +280,11 @@ Widget addOrEditBottomSheet(SalesInProvinceLogic controller, {bool isEditMode =
onPressed: isEditMode
? () async {
await controller.updateAllocation();
Get.back();
}
: () async {
await controller.submitAllocation();
Get.back();
},
);
}, controller.isValid),
@@ -365,3 +376,140 @@ Widget productDropDown(SalesInProvinceLogic controller) {
);
});
}
Container modalDatePicker(ValueChanged<Jalali> onDateSelected) {
Jalali? tempPickedDate;
return Container(
height: 250,
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: Row(
children: [
SizedBox(width: 20),
RElevated(
height: 35,
width: 70,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'تایید',
),
Spacer(),
RElevated(
height: 35,
width: 70,
backgroundColor: AppColor.error,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'لغو',
),
SizedBox(width: 20),
],
),
),
Divider(height: 0, thickness: 1),
Expanded(
child: Container(
child: PersianCupertinoDatePicker(
initialDateTime: Jalali.now(),
minimumDate: Jalali.now().add(days: -1),
maximumDate: Jalali.now(),
mode: PersianCupertinoDatePickerMode.date,
onDateTimeChanged: (dateTime) {
tempPickedDate = dateTime;
},
),
),
),
],
),
);
}
Widget show2StepAddBottomSheet(SalesInProvinceLogic controller) {
return BaseBottomSheet(
height: Get.height * .39,
child: Column(
spacing: 8,
children: [
buildRow(
title: 'تاریخ ثبت',
value: controller.tmpStewardAllocation?.date?.formattedJalaliDate ?? 'ندارد',
),
buildRow(
title: 'نام و نام خانوادگی خریدار',
value:
controller.guildsModel
.firstWhere((p0) => p0.key == controller.tmpStewardAllocation?.guildKey)
.user
?.fullname ??
'ندارد',
),
buildRow(
title: 'شماره خریدار',
value:
controller.guildsModel
.firstWhere((p0) => p0.key == controller.tmpStewardAllocation?.guildKey)
.user
?.mobile ??
'ندارد',
),
buildRow(
title: 'قیمت هر کیلو',
value: '${controller.tmpStewardAllocation?.amount.separatedByCommaFa ?? 0} ریال ',
),
buildRow(
title: 'وزن تخصیصی',
value:
'${controller.tmpStewardAllocation?.weightOfCarcasses?.toInt().separatedByCommaFa ?? 0} کیلوگرم',
),
buildRow(
title: 'قیمت کل',
value: '${controller.tmpStewardAllocation?.totalAmount.separatedByCommaFa ?? 0} ریال',
),
Row(
spacing: 10,
children: [
Expanded(
child: RElevated(
backgroundColor: AppColor.greenNormal,
height: 40,
text: 'ثبت',
textStyle: AppFonts.yekan18.copyWith(color: Colors.white),
onPressed: () async {
await controller.submitAllocation();
Get
..back()
..back();
},
),
),
Expanded(
child: ROutlinedElevated(
height: 40,
borderColor: AppColor.error,
text: ' بازگشت',
textStyle: AppFonts.yekan18.copyWith(color: AppColor.error),
onPressed: () {
Get
..back()
..back();
},
),
),
],
),
],
),
);
}

View File

@@ -357,4 +357,26 @@ class SalesOutOfProvinceLogic extends GetxController {
onError: (error, stacktrace) {},
);
}
void setSaleDate(Jalali value) {
saleDate.value = value;
saleDate.refresh();
dateErrorDialog();
}
void setProductionDate(DayInfo value) {
productionDate.value = value.date;
remainingStock.value = value.remainingStock;
dateErrorDialog();
}
void dateErrorDialog() {
if ((productionDate.value?.distanceTo(saleDate.value) ?? 0) >= 1) {
saleDate.value = Jalali.now();
Future.delayed(
Duration(milliseconds: 300),
() => defaultShowErrorMessage("تاریخ تولید نمی تواند قبل از تاریخ فروش باشد"),
);
}
}
}

View File

@@ -352,14 +352,21 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
child: Column(
spacing: 12,
children: [
RTextField(
controller: TextEditingController(),
filledColor: AppColor.bgLight,
label: 'تاریخ',
readonly: true,
borderColor: AppColor.darkGreyLight,
initText: Jalali.now().formatCompactDate(),
),
ObxValue((data) {
return RTextField(
controller: TextEditingController(),
filledColor: AppColor.bgLight,
filled: true,
label: 'تاریخ',
onTap: () {
Get.bottomSheet(
modalDatePicker((value) => controller.setSaleDate(value)),
);
},
borderColor: AppColor.darkGreyLight,
initText: data.value.formatCompactDate(),
);
}, controller.saleDate),
Visibility(
visible: isOnEdit == false,
child: Container(
@@ -430,8 +437,7 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
label: 'تاریخ تولید گوشت',
selectedDate: controller.productionDate.value?.formatCompactDate(),
onDateSelect: (value) {
controller.productionDate.value = value.date;
controller.remainingStock.value = value.remainingStock;
controller.setProductionDate(value);
},
dayData: controller.quotaType.value == 1
? controller.governmentalProductionDateData