Fixed tests for Tablet/Desktop layout

This commit is contained in:
Gianfranco Gasbarri 2020-06-11 19:30:24 +01:00
parent fad460dc7b
commit 4557c7e1ec
10 changed files with 368 additions and 299 deletions

View File

@ -61,3 +61,29 @@ class ActionMenuButton extends StatelessWidget {
);
}
}
/// This class is used to differentiate List and View ActionMenuButtons
/// during tests
class ViewActionMenuButton extends StatelessWidget {
const ViewActionMenuButton({
@required this.entity,
@required this.onSelected,
this.isSaving = false,
this.entityActions,
});
final BaseEntity entity;
final List<EntityAction> entityActions;
final Function(BuildContext, EntityAction) onSelected;
final bool isSaving;
@override
Widget build(BuildContext context) {
return ActionMenuButton(
entity: entity,
onSelected: onSelected,
isSaving: isSaving,
entityActions: entityActions
);
}
}

View File

@ -335,6 +335,7 @@ class _AppBottomBarState extends State<AppBottomBar> {
final prefState = store.state.prefState;
final isList = prefState.moduleLayout == ModuleLayout.list ||
widget.entityType.isSetting;
final company = state.company;
return BottomAppBar(
shape: CircularNotchedRectangle(),
@ -384,7 +385,6 @@ class _AppBottomBarState extends State<AppBottomBar> {
),
if (widget.statuses.isNotEmpty)
IconButton(
tooltip: localization.filter,
icon: Icon(Icons.filter),
onPressed: _showFilterStatusSheet,
color: store.state
@ -395,7 +395,6 @@ class _AppBottomBarState extends State<AppBottomBar> {
),
if (widget.customValues1.isNotEmpty)
IconButton(
tooltip: localization.filter,
icon: Icon(Icons.looks_one),
onPressed: _showFilterCustom1Sheet,
color: store.state
@ -406,7 +405,6 @@ class _AppBottomBarState extends State<AppBottomBar> {
),
if (widget.customValues2.isNotEmpty)
IconButton(
tooltip: localization.filter,
icon: Icon(Icons.looks_two),
onPressed: _showFilterCustom2Sheet,
color: store.state
@ -417,7 +415,6 @@ class _AppBottomBarState extends State<AppBottomBar> {
),
if (widget.customValues3.isNotEmpty)
IconButton(
tooltip: localization.filter,
icon: Icon(Icons.looks_two),
onPressed: _showFilterCustom3Sheet,
color: store.state
@ -428,7 +425,6 @@ class _AppBottomBarState extends State<AppBottomBar> {
),
if (widget.customValues4.isNotEmpty)
IconButton(
tooltip: localization.filter,
icon: Icon(Icons.looks_two),
onPressed: _showFilterCustom4Sheet,
color: store.state

View File

@ -120,6 +120,7 @@ class _EntityDropdownState extends State<EntityDropdown> {
alignment: Alignment.centerRight,
children: <Widget>[
TypeAheadFormField<String>(
validator: widget.validator,
noItemsFoundBuilder: (context) => SizedBox(),
suggestionsBoxDecoration: SuggestionsBoxDecoration(
constraints: BoxConstraints(

View File

@ -243,60 +243,70 @@ class MenuDrawer extends StatelessWidget {
entityType: EntityType.client,
icon: getEntityIcon(EntityType.client),
title: localization.clients,
iconTooltip: localization.newClient,
),
DrawerTile(
company: company,
entityType: EntityType.product,
icon: getEntityIcon(EntityType.product),
title: localization.products,
iconTooltip: localization.newProduct,
),
DrawerTile(
company: company,
entityType: EntityType.invoice,
icon: getEntityIcon(EntityType.invoice),
title: localization.invoices,
iconTooltip: localization.newInvoice,
),
DrawerTile(
company: company,
entityType: EntityType.payment,
icon: getEntityIcon(EntityType.payment),
title: localization.payments,
iconTooltip: localization.newPayment,
),
DrawerTile(
company: company,
entityType: EntityType.quote,
icon: getEntityIcon(EntityType.quote),
title: localization.quotes,
iconTooltip: localization.newQuote,
),
DrawerTile(
company: company,
entityType: EntityType.credit,
icon: getEntityIcon(EntityType.credit),
title: localization.credits,
iconTooltip: localization.newCredit,
),
DrawerTile(
company: company,
entityType: EntityType.project,
icon: getEntityIcon(EntityType.project),
title: localization.projects,
iconTooltip: localization.newProject,
),
DrawerTile(
company: company,
entityType: EntityType.task,
icon: getEntityIcon(EntityType.task),
title: localization.tasks,
iconTooltip: localization.newTask,
),
DrawerTile(
company: company,
entityType: EntityType.vendor,
icon: getEntityIcon(EntityType.vendor),
title: localization.vendors,
iconTooltip: localization.newVendor,
),
DrawerTile(
company: company,
entityType: EntityType.expense,
icon: getEntityIcon(EntityType.expense),
title: localization.expenses,
iconTooltip: localization.newExpense,
),
// STARTER: menu - do not remove comment
DrawerTile(
@ -349,6 +359,7 @@ class DrawerTile extends StatefulWidget {
this.entityType,
this.onLongPress,
this.onCreateTap,
this.iconTooltip,
});
final CompanyEntity company;
@ -358,6 +369,7 @@ class DrawerTile extends StatefulWidget {
final Function onTap;
final Function onLongPress;
final Function onCreateTap;
final String iconTooltip;
@override
_DrawerTileState createState() => _DrawerTileState();
@ -421,6 +433,7 @@ class _DrawerTileState extends State<DrawerTile> {
);
} else if (userCompany.canCreate(widget.entityType)) {
trailingWidget = IconButton(
tooltip: widget.iconTooltip,
icon: Icon(
Icons.add_circle_outline,
color: textColor,

View File

@ -88,7 +88,7 @@ class ViewScaffold extends StatelessWidget {
);
})
: Container(),
ActionMenuButton(
ViewActionMenuButton(
isSaving: state.isSaving,
entity: entity,
onSelected: (context, action) =>

View File

@ -237,7 +237,7 @@ class DashboardPanels extends StatelessWidget {
currentData: currentData,
previousData: previousData,
isLoaded: isLoaded,
title: AppLocalization.of(context).invoices);
title: AppLocalization.of(context).invoiced);
}
Widget _paymentChart(BuildContext context) {

View File

@ -42,8 +42,6 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
TabController _tabController;
FocusNode _focusNode;
static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_invoiceDesktopEdit');
final _invoiceNumberController = TextEditingController();
final _poNumberController = TextEditingController();
@ -173,10 +171,7 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
final invoice = viewModel.invoice;
final company = viewModel.company;
return AppForm(
formKey: _formKey,
focusNode: _focusNode,
child: ListView(
return ListView(
key: ValueKey('__invoice_${invoice.id}__'),
children: <Widget>[
Row(
@ -286,10 +281,9 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
: widget.entityType == EntityType.quote
? localization.quoteNumber
: localization.invoiceNumber,
validator: (String val) =>
val.trim().isEmpty && invoice.isOld
? AppLocalization.of(context)
.pleaseEnterAnInvoiceNumber
validator: (String val) => val.trim().isEmpty &&
invoice.isOld
? AppLocalization.of(context).pleaseEnterAnInvoiceNumber
: null,
),
DecoratedFormField(
@ -432,16 +426,16 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
),
if (company.settings.enableSecondInvoiceTaxRate)
TaxRateDropdown(
onSelected: (taxRate) => viewModel.onChanged(
invoice.applyTax(taxRate, isSecond: true)),
onSelected: (taxRate) => viewModel
.onChanged(invoice.applyTax(taxRate, isSecond: true)),
labelText: localization.tax,
initialTaxName: invoice.taxName2,
initialTaxRate: invoice.taxRate2,
),
if (company.settings.enableThirdInvoiceTaxRate)
TaxRateDropdown(
onSelected: (taxRate) => viewModel.onChanged(
invoice.applyTax(taxRate, isThird: true)),
onSelected: (taxRate) => viewModel
.onChanged(invoice.applyTax(taxRate, isThird: true)),
labelText: localization.tax,
initialTaxName: invoice.taxName3,
initialTaxRate: invoice.taxRate3,
@ -459,7 +453,6 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
],
),
],
),
);
}
}

View File

@ -136,6 +136,7 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
Padding(
padding: const EdgeInsets.only(right: kTableColumnGap),
child: TypeAheadFormField<String>(
key: ValueKey('__line_item_${index}_name__'),
initialValue: lineItems[index].productKey,
noItemsFoundBuilder: (context) => SizedBox(),
suggestionsCallback: (pattern) {
@ -238,6 +239,7 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
Padding(
padding: const EdgeInsets.only(right: kTableColumnGap),
child: TextFormField(
key: ValueKey('__line_item_${index}_description__'),
initialValue: lineItems[index].notes,
onChanged: (value) => viewModel.onChangedInvoiceItem(
lineItems[index].rebuild((b) => b..notes = value),
@ -346,6 +348,7 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
Padding(
padding: const EdgeInsets.only(right: kTableColumnGap),
child: TextFormField(
key: ValueKey('__line_item_${index}_cost__'),
textAlign: TextAlign.right,
initialValue: formatNumber(lineItems[index].cost, context,
formatNumberType: FormatNumberType.input,
@ -361,6 +364,7 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
Padding(
padding: const EdgeInsets.only(right: kTableColumnGap),
child: TextFormField(
key: ValueKey('__line_item_${index}_quantity__'),
textAlign: TextAlign.right,
initialValue: formatNumber(
lineItems[index].quantity, context,

View File

@ -18,6 +18,8 @@ void runTestSuite({bool batchMode = false}) {
final poNumber =
faker.randomGenerator.integer(999999, min: 100000).toString();
final productKey = makeUnique(faker.food.cuisine());
final clientKey = faker.randomGenerator.integer(999999, min: 100000)
.toString();
final description = faker.lorem.sentences(5).toString();
final cost =
faker.randomGenerator.decimal(min: 50, scale: 10).toStringAsFixed(2);
@ -67,21 +69,30 @@ void runTestSuite({bool batchMode = false}) {
// Create a new invoice
test('Add a new invoice', () async {
print('Tap new invoice');
await driver.tap(find.byTooltip(localization.newInvoice));
print('Create new client: $clientName');
if(await isMobile(driver)) {
await driver.tap(find.byValueKey(Keys.clientPickerEmptyKey));
}
await driver.tap(find.byTooltip(localization.createNew));
print('Fill the client form');
await fillTextField(
driver: driver, field: localization.name, value: clientName);
await fillTextFields(driver, <String, String>{
localization.name: clientName,
localization.idNumber: clientKey
});
// Await for Debouncer
await Future<dynamic>.delayed(Duration(milliseconds: 500));
await driver.tap(find.text(localization.save));
// Await for Screen change
await driver.waitFor(find.text(localization.newInvoice));
print('Fill the invoice form');
if(await isMobile(driver)) {
await driver.tap(find.byTooltip(localization.addItem));
await driver.tap(find.byTooltip(localization.createNew));
@ -97,6 +108,15 @@ void runTestSuite({bool batchMode = false}) {
await driver.tap(find.text(localization.done));
await driver.tap(find.text(localization.details));
} else {
await fillTextFields(driver, <String, String>{
getLineItemKey('name', 0): productKey,
getLineItemKey('description', 0): description,
getLineItemKey('cost', 0): cost,
getLineItemKey('quantity', 0): '1'
});
}
await fillAndSaveForm(driver, <String, String>{
localization.poNumber: poNumber,
});
@ -140,7 +160,8 @@ void runTestSuite({bool batchMode = false}) {
await selectAction(driver, localization.enterPayment);
await driver.tap(find.text(localization.save));
// "Completed" status
await driver.waitFor(find.text(localization.paymentStatus4.toUpperCase()));
await driver.waitFor(
find.text(localization.paymentStatus4.toUpperCase()));
if (await isMobile(driver)) {
await driver.tap(find.pageBack());

View File

@ -9,6 +9,7 @@ import 'localizations.dart';
class Keys {
static const String openAppDrawer = 'Open navigation menu';
static const String clientPickerEmptyKey = '__client___';
static const String invoiceLineItemBaseKey = '__line_item';
}
Future<bool> isTablet(FlutterDriver driver) async {
@ -129,15 +130,15 @@ Future<void> fillAndSaveForm(FlutterDriver driver, Map<String, dynamic> values,
// Await for Debouncer
await Future<dynamic>.delayed(Duration(milliseconds: 400));
print('Check for updated values');
await checkTextFields(driver, values, except: skipCheckFor);
print('Tap save');
await driver.tap(find.text(localization.save));
// verify snackbar
//await driver.waitFor(find.text(localization.updatedProduct));
//await driver.tap(find.pageBack());
print('Check for updated values');
await checkTextFields(driver, values, except: skipCheckFor);
}
Future<void> testArchiveAndDelete(
@ -146,32 +147,46 @@ Future<void> testArchiveAndDelete(
String deletedMessage,
String restoredMessage}) async {
final localization = TestLocalization('en');
final mobile = await isMobile(driver);
if (!mobile) {
// Show archived and deleted entries on tablet/web
await driver.tap(find.byTooltip(localization.filter));
await driver.tap(find.text(localization.archived));
await driver.tap(find.text(localization.deleted));
await driver.tap(find.byTooltip(localization.filter));
}
print('Archive record');
await selectAction(driver, localization.archive);
await driver.waitFor(find.text(archivedMessage));
await driver.waitFor(find.text(localization.archived));
//await driver.waitFor(find.text(localization.archived));
print('Restore record');
await selectAction(driver, localization.restore);
await driver.waitFor(find.text(restoredMessage));
await driver.waitForAbsent(find.text(localization.archived));
await driver.waitForAbsent(find.byType('Snackbar'));
print('Delete record');
await selectAction(driver, localization.delete);
await driver.waitFor(find.text(deletedMessage));
await driver.waitFor(find.text(localization.deleted));
//await driver.waitFor(find.text(localization.deleted));
print('Restore record');
await selectAction(driver, localization.restore);
await driver.waitFor(find.text(restoredMessage));
await driver.waitForAbsent(find.text(localization.deleted));
await driver.waitForAbsent(find.byType('Snackbar'));
}
Future<void> selectAction(FlutterDriver driver, String action) async {
await driver.tap(find.byType('ActionMenuButton'));
await driver.tap(find.byType('ViewActionMenuButton'));
await driver.tap(find.text(action));
}
String makeUnique(String value) =>
'$value ${faker.randomGenerator.integer(999999, min: 100000)}';
String getLineItemKey(String key, int index) =>
'${Keys.invoiceLineItemBaseKey}_${index}_${key}__';