In my app, the widget tree is DeleteItem GestureDetector -> InvoiceTotal Widget -> ItemListWidget -> ListView -> EditInvoiceClass
I tried to use PatrolTest
await $(#InvoiceTotal).$(#DeleteItem).scrollTo().tap();
$.pumpAndSettle;
to test the DeleteItem GestureDetector but I got an Exception --
WaitUntilVisibleTimeoutException (TimeoutException after 0:00:10.000000: Finder exactly one widget with key \[\<'DeleteItem'\>\] that has ancestor(s) with key \[\<'InvoiceTotal'\>\] (ignoring all but first) (ignoring offstage widgets): GestureDetector-\[\<'DeleteItem'\>\](startBehavior: start, dependencies: \[MediaQuery, \_ScrollableScope\]) did not find any visible widgets)
I want to know why the DeleteItem GestureDetector is invisible? How can I fix the problem?
My Flutter code attached below.
// Code Segment of EditInvoice Class
return Consumer<ItemContents>(
builder: (context, value, child) {
return Consumer<DarkThemeProvider>(
builder: (context, value1, child1) {
return Expanded(
child: Column(
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: myTheme.normalPadding),
child: const goBack(),
),
Expanded(
child: Scrollbar(
child: ListView(
padding: EdgeInsets.zero,
children: [
Padding(
padding: EdgeInsets.all(myTheme.normalPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
ItemListWidget(
key: const Key('ItemListWidget'),
context: context,
myTextTheme: myTextTheme,
myTextColor: myTextColor,
value: value,
myTheme: myTheme,
invoicesContents: vm.invoicesData(
vm,
newAddModel,
editData,
index,
),
),
vm.isEmptyInvoice
? RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: "New Invoice",
style: myTheme.textTheme(
"labelLarge", isDark),
),
],
),
)
: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: "Edit ",
style: myTheme.textTheme(
"labelLarge", isDark),
),
TextSpan(
text: "#",
style: myTheme.textTheme(
"labelSmall", isDark),
),
TextSpan(
text: vm.selectedItem.id,
style: myTheme.textTheme(
"labelLarge", isDark),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 24.0),
child: Text(
'Bill From',
style: myTheme.textTheme("caption", isDark),
),
),
Form(
key: const Key('form'),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
// InvoiceFadeWidget(
// myTheme: myTheme,
// key: const Key('fadeWidget'),
// ),
InvoicesPageTextField(
class ItemListWidget extends StatelessWidget {
const ItemListWidget({
Key? key = const Key('ItemListWidget'),
required this.context,
required this.myTextTheme,
required this.myTextColor,
required this.value,
required this.myTheme,
required this.invoicesContents,
}) : super(key: const Key('ItemListWidget'),);
final BuildContext context;
final TextTheme myTextTheme;
final ThemeData myTextColor;
final ItemContents value;
final DarkThemeProvider myTheme;
final InvoicesContents invoicesContents;
@override
Widget build(BuildContext context) {
return Column(
children: [
for (var i = 0; i < invoicesContents.items.length; i++)
Padding(
padding: EdgeInsets.only(bottom: myTheme.normalPadding * 2),
child: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: myTheme.normalPadding),
child: InvoicesPageTextField(
key: const Key('ItemName'),
labelText: 'Item Name',
controller: TextEditingController(
text: invoicesContents.items[i].name,
),
onChanged: ((p0) {
invoicesContents.items[i].name = p0;
}),
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: myTheme.normalPadding * 3 - 8,
child: InvoicesPageTextField(
key: const Key('Qty'),
labelText: 'Qty.',
controller: TextEditingController(
text: invoicesContents.items[i].quantity.toString(),
),
onChanged: (p0) {
int? qty = int.tryParse(p0);
if (qty != null && qty > 0) {
invoicesContents.items[i].quantity = qty;
invoicesContents.items[i].total =
// ignore: unnecessary_cast
(qty *
invoicesContents.items[i].price.toDouble());
invoicesContents.total = invoicesContents.items
.map((e) => e.total)
.reduce((a, b) => a + b);
context.read<ItemContents>().refresh();
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).canvasColor,
title: Text(
'Ops! =v=',
style: myTheme.textTheme(
"bodyMedium", myTheme.darkTheme),
),
content: Text(
'Natural Number Required !',
style: myTheme.textTheme(
"displayLarge", myTheme.darkTheme),
),
actionsPadding: EdgeInsets.symmetric(
horizontal: myTheme.normalPadding / 2 - 2,
vertical: myTheme.normalPadding / 2 - 2,
),
actions: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(myTheme.normalPadding),
),
color: myTextColor.highlightColor,
),
width: myTheme.normalPadding * 4 - 7,
height: myTheme.normalPadding * 2,
child: GestureDetector(
key: const Key('EnterNaturalNum'),
behavior: HitTestBehavior.opaque,
onTap: () => Navigator.pop(context),
child: Container(
alignment: Alignment.center,
child: Text(
'OK',
style: myTheme.textTheme(
"headlineMedium", myTheme.darkTheme),
),
),
),
),
],
),
);
}
},
),
),
InvoicePriceWidget(
key: const Key('InvoicePrice'),
context: context,
i: i,
invoicesContents: invoicesContents,
myTheme: myTheme),
InvoiceTotalWidget(
key: const Key('InvoiceTotal'),
myTextTheme: myTextTheme,
i: i,
context: context,
myTheme: myTheme,
invoicesContents: invoicesContents),
],
)
],
),
),
],
);
}
}
class InvoiceTotalWidget extends StatelessWidget {
const InvoiceTotalWidget({
Key? key = const Key('InvoiceTotal'),
required this.myTextTheme,
required this.i,
required this.context,
required this.myTheme,
required this.invoicesContents,
}) : super(
key: const Key('InvoiceTotal'),
);
final TextTheme myTextTheme;
final int i;
final BuildContext context;
final DarkThemeProvider myTheme;
final InvoicesContents invoicesContents;
@override
Widget build(BuildContext context) {
return Expanded(
key: const Key('TotalWidget'),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(bottom: myTheme.normalPadding),
child: Text(
'Total',
style: myTheme.textTheme("captionSmall", myTheme.darkTheme),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
context.read<ItemContents>().parseTotal(
invoicesContents.items[i].total.toDouble(),
showCurrency: false),
style: myTheme.textTheme("displayLarge", myTheme.darkTheme),
),
GestureDetector(
key: const Key('DeleteItem'),
onTap: () {
//const Key('DeleteItemOnTap');
invoicesContents.total = invoicesContents.total -
invoicesContents.items[i].quantity *
invoicesContents.items[i].price;
invoicesContents.items[i].quantity = 0;
invoicesContents.items[i].price = 0;
context.read<ItemContents>().refresh();
invoicesContents.items.removeAt(i);
context
.read<ItemContents>()
.data[context.read<ItemContents>().data.length - 1] =
invoicesContents;
context.read<ItemContents>().refresh();
},
child: Padding(
key: const Key('DeleteIcon'),
padding: EdgeInsets.only(right: myTheme.normalPadding * 0.9),
child: SvgPicture.asset(
'assets/images/icon-delete.svg',
),
),
),
],
),
],
),
);
}
}
I have tried to move the position of this button to the front of the ListView to ensure that the button has been generated. But this still does not make the button visible.
Try adding the key
const Key('EnterNaturalNum')
to theText
widget itself, not to theGestureDetector
.Alternatively, you can try setting
GestureDetector.behavior
to HitTestBehavior.opqaue.