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

@@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rasadyar_core/presentation/widget/overlay_dropdown_widget/multi_select_dropdown/multi_select_dropdown_logic.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('SearchableDropdownLogic', () {
test('initializes selectedItem from provided list', () {
final logic = SearchableDropdownLogic<String>(
items: const ['A', 'B'],
selectedItem: const ['A'],
itemBuilder: (i) => Text(i),
);
expect(logic.selectedItem, ['A']);
});
test('initializes selectedItem from initialValue when single select', () {
final logic = SearchableDropdownLogic<String>(
items: const ['A', 'B'],
singleSelect: true,
initialValue: 'B',
itemBuilder: (i) => Text(i),
);
expect(logic.selectedItem, ['B']);
});
testWidgets('showOverlay sets isOpen and inserts overlay', (tester) async {
final logic = SearchableDropdownLogic<String>(
items: const ['A', 'B'],
itemBuilder: (i) => Text(i),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (context) {
return CompositedTransformTarget(
link: logic.layerLink,
child: GestureDetector(
onTap: () => logic.showOverlay(context),
child: const SizedBox(width: 200, height: 40),
),
);
},
),
),
),
);
expect(logic.isOpen.value, false);
await tester.tap(find.byType(GestureDetector));
await tester.pump();
expect(logic.isOpen.value, true);
expect(find.text('نتیجه‌ای یافت نشد.'), findsNothing);
});
testWidgets('removeOverlay resets isOpen', (tester) async {
final logic = SearchableDropdownLogic<String>(
items: const ['A', 'B'],
itemBuilder: (i) => Text(i),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (context) {
return CompositedTransformTarget(
link: logic.layerLink,
child: GestureDetector(
onTap: () {
if (!logic.isOpen.value) {
logic.showOverlay(context);
} else {
logic.removeOverlay();
}
},
child: const SizedBox(width: 200, height: 40),
),
);
},
),
),
),
);
await tester.tap(find.byType(GestureDetector));
await tester.pump();
expect(logic.isOpen.value, true);
await tester.tap(find.byType(GestureDetector));
await tester.pump();
expect(logic.isOpen.value, false);
});
testWidgets('tap item adds to selected and calls onChanged', (tester) async {
String? changed;
final logic = SearchableDropdownLogic<String>(
items: const ['A', 'B'],
onChanged: (s) => changed = s,
itemBuilder: (i) => Text(i),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (context) {
return CompositedTransformTarget(
link: logic.layerLink,
child: GestureDetector(
onTap: () => logic.showOverlay(context),
child: const SizedBox(width: 200, height: 40),
),
);
},
),
),
),
);
await tester.tap(find.byType(GestureDetector));
await tester.pump();
await tester.tap(find.text('A'));
await tester.pump();
expect(logic.selectedItem.contains('A'), true);
expect(changed, 'A');
});
testWidgets('singleSelect updates searchController text using labelBuilder', (tester) async {
final logic = SearchableDropdownLogic<String>(
items: const ['A', 'B'],
singleSelect: true,
labelBuilder: (s) => 'L:$s',
itemBuilder: (i) => Text(i),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (context) {
return CompositedTransformTarget(
link: logic.layerLink,
child: GestureDetector(
onTap: () => logic.showOverlay(context),
child: const SizedBox(width: 200, height: 40),
),
);
},
),
),
),
);
await tester.tap(find.byType(GestureDetector));
await tester.pump();
await tester.tap(find.text('B'));
await tester.pump();
expect(logic.searchController.text, 'L:B');
});
testWidgets('performSearch uses onSearch and updates filteredItems', (tester) async {
final logic = SearchableDropdownLogic<String>(
items: const ['A', 'B', 'C'],
onSearch: (q) async => ['B'],
itemBuilder: (i) => Text(i),
);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (context) {
return CompositedTransformTarget(
link: logic.layerLink,
child: GestureDetector(
onTap: () => logic.showOverlay(context),
child: const SizedBox(width: 200, height: 40),
),
);
},
),
),
),
);
await tester.tap(find.byType(GestureDetector));
await tester.pump();
logic.performSearch('x');
await tester.pumpAndSettle();
expect(logic.filteredItems, ['B']);
});
});
}

View File

@@ -0,0 +1,139 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rasadyar_core/presentation/widget/overlay_dropdown_widget/multi_select_dropdown/multi_select_dropdown.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('SearchableDropdown widget', () {
testWidgets('asserts builders based on singleSelect', (tester) async {
expect(
() => SearchableDropdown<String>(
items: const ['A', 'B'],
itemBuilder: Text.new,
singleSelect: true,
),
throwsA(isA<AssertionError>()),
);
expect(
() => SearchableDropdown<String>(
items: const ['A', 'B'],
itemBuilder: Text.new,
singleSelect: false,
),
throwsA(isA<AssertionError>()),
);
});
testWidgets('single select: selecting item sets text and calls onChanged', (tester) async {
String? changed;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SearchableDropdown<String>(
items: const ['A', 'B'],
singleSelect: true,
singleLabelBuilder: (s) => 'L:$s',
itemBuilder: Text.new,
onChanged: (v) => changed = v,
),
),
),
);
await tester.tap(find.byType(TextField));
await tester.pump();
await tester.tap(find.text('A'));
await tester.pump();
final tf = tester.widget<TextField>(find.byType(TextField));
expect(tf.controller!.text, 'L:A');
expect(changed, 'A');
});
testWidgets('multi select: shows chips and allows removing selection', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SearchableDropdown<String>(
items: const ['A', 'B', 'C'],
singleSelect: false,
multiLabelBuilder: (s) => Container(
key: Key('chip_${s ?? ''}'),
padding: const EdgeInsets.all(4),
child: Text(s ?? ''),
),
itemBuilder: Text.new,
),
),
),
);
await tester.tap(find.byType(TextField));
await tester.pump();
await tester.tap(find.text('A'));
await tester.pump();
await tester.tap(find.byType(TextField));
await tester.pump();
await tester.tap(find.text('B'));
await tester.pump();
expect(find.byKey(const Key('chip_A')), findsOneWidget);
expect(find.byKey(const Key('chip_B')), findsOneWidget);
await tester.tap(find.byKey(const Key('chip_A')));
await tester.pump();
expect(find.byKey(const Key('chip_A')), findsNothing);
expect(find.byKey(const Key('chip_B')), findsOneWidget);
});
testWidgets('readOnly toggles based on onSearch presence', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Column(
children: [
SearchableDropdown<String>(
items: const ['A'],
singleSelect: true,
singleLabelBuilder: (s) => s ?? '',
itemBuilder: Text.new,
),
SearchableDropdown<String>(
items: const ['A'],
singleSelect: true,
singleLabelBuilder: (s) => s ?? '',
itemBuilder: Text.new,
onSearch: (q) async => ['A'],
),
],
),
),
),
);
final textFields = tester.widgetList<TextField>(find.byType(TextField)).toList();
expect(textFields[0].readOnly, true);
expect(textFields[1].readOnly, false);
});
testWidgets('tapping TextField toggles overlay list visibility', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SearchableDropdown<String>(
items: const ['A', 'B'],
singleSelect: true,
singleLabelBuilder: (s) => s ?? '',
itemBuilder: Text.new,
),
),
),
);
expect(find.text('A'), findsNothing);
await tester.tap(find.byType(TextField));
await tester.pump();
expect(find.text('A'), findsOneWidget);
await tester.tap(find.text('A'));
await tester.pump();
expect(find.text('A'), findsNothing);
});
});
}