How to test this GestureDetector in Flutter with Patrol?

568 Views Asked by At

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.

1

There are 1 best solutions below

0
On

Try adding the key const Key('EnterNaturalNum') to the Text widget itself, not to the GestureDetector.

Alternatively, you can try setting GestureDetector.behavior to HitTestBehavior.opqaue.