Allow deleting templates
This commit is contained in:
parent
058291fa80
commit
384aa4eb6f
@ -15,4 +15,18 @@ class RecurringTransaction {
|
|||||||
final template = IsarLink<TransactionTemplate>();
|
final template = IsarLink<TransactionTemplate>();
|
||||||
|
|
||||||
final account = IsarLink<Account>();
|
final account = IsarLink<Account>();
|
||||||
|
|
||||||
|
bool isDue(DateTime now) {
|
||||||
|
if (lastExecution == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final expectedNextExecution = lastExecution!.add(Duration(days: days));
|
||||||
|
if (now.isAfter(expectedNextExecution)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return now.difference(expectedNextExecution).inDays.abs() <=
|
||||||
|
(days * 0.5).toInt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,9 +83,7 @@ Stream<void> watchRecurringTransactions(Account account) {
|
|||||||
Future<void> upsertAccount(Account account) async {
|
Future<void> upsertAccount(Account account) async {
|
||||||
final db = GetIt.I.get<Isar>();
|
final db = GetIt.I.get<Isar>();
|
||||||
return db.writeTxn(() async {
|
return db.writeTxn(() async {
|
||||||
print("Before account insert");
|
await db.accounts.put(account);
|
||||||
final id = await db.accounts.put(account);
|
|
||||||
print("After account insert: $id");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +114,14 @@ Future<void> upsertTransactionTemplate(TransactionTemplate template) async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteRecurringTransactionTemplate(RecurringTransaction template) {
|
||||||
|
final db = GetIt.I.get<Isar>();
|
||||||
|
return db.writeTxn(() async {
|
||||||
|
await db.transactionTemplates.delete(template.template.value!.id);
|
||||||
|
await db.recurringTransactions.delete(template.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> upsertRecurringTransaction(RecurringTransaction template) async {
|
Future<void> upsertRecurringTransaction(RecurringTransaction template) async {
|
||||||
final db = GetIt.I.get<Isar>();
|
final db = GetIt.I.get<Isar>();
|
||||||
return db.writeTxn(() async {
|
return db.writeTxn(() async {
|
||||||
@ -148,22 +154,24 @@ Stream<void> watchTransactionTemplates(Account account) {
|
|||||||
.watchLazy(fireImmediately: true);
|
.watchLazy(fireImmediately: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<TransactionTemplate>> getTransactionTemplates(
|
Future<void> deleteTransactionTemplate(TransactionTemplate template) {
|
||||||
Account? account,
|
final db = GetIt.I.get<Isar>();
|
||||||
) async {
|
return db.writeTxn(() async {
|
||||||
|
await db.transactionTemplates.delete(template.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TransactionTemplate>> getTransactionTemplates(Account? account) {
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
return Future.value([]);
|
return Future.value([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
final a =
|
return GetIt.I
|
||||||
await GetIt.I
|
.get<Isar>()
|
||||||
.get<Isar>()
|
.transactionTemplates
|
||||||
.transactionTemplates
|
.filter()
|
||||||
.filter()
|
.account((q) => q.idEqualTo(account.id))
|
||||||
.account((q) => q.idEqualTo(account.id))
|
.findAll();
|
||||||
.findAll();
|
|
||||||
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<void> watchTransactions(Account account) {
|
Stream<void> watchTransactions(Account account) {
|
||||||
|
@ -1,26 +1,24 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:okane/database/collections/recurrent.dart';
|
import 'package:okane/database/collections/recurrent.dart';
|
||||||
|
import 'package:okane/database/database.dart';
|
||||||
import 'package:okane/ui/state/core.dart';
|
import 'package:okane/ui/state/core.dart';
|
||||||
|
import 'package:okane/ui/utils.dart';
|
||||||
|
import 'package:okane/ui/widgets/add_transaction.dart';
|
||||||
|
|
||||||
class UpcomingTransactionsCard extends StatelessWidget {
|
class UpcomingTransactionsCard extends StatelessWidget {
|
||||||
const UpcomingTransactionsCard({super.key});
|
const UpcomingTransactionsCard({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final bloc = GetIt.I.get<CoreCubit>();
|
||||||
return BlocBuilder<CoreCubit, CoreState>(
|
return BlocBuilder<CoreCubit, CoreState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final today = DateTime.now();
|
final today = DateTime.now();
|
||||||
final upcomingRaw =
|
final upcomingRaw =
|
||||||
state.recurringTransactions.where((t) {
|
state.recurringTransactions.where((t) => t.isDue(today)).toList();
|
||||||
if (t.lastExecution == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return today.difference(t.lastExecution!).inDays <=
|
|
||||||
(t.days * 1.5).toInt();
|
|
||||||
}).toList();
|
|
||||||
final List<RecurringTransaction> upcoming =
|
final List<RecurringTransaction> upcoming =
|
||||||
upcomingRaw.isEmpty
|
upcomingRaw.isEmpty
|
||||||
? List.empty()
|
? List.empty()
|
||||||
@ -53,7 +51,23 @@ class UpcomingTransactionsCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: Icon(Icons.play_arrow),
|
icon: Icon(Icons.play_arrow),
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
showDialogOrModal(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => AddTransactionWidget(
|
||||||
|
activeAccountItem: bloc.activeAccount!,
|
||||||
|
template: t.template.value!,
|
||||||
|
onAdd: (transaction) async {
|
||||||
|
// Update the recurring template
|
||||||
|
print(transaction.date);
|
||||||
|
t.lastExecution = transaction.date;
|
||||||
|
await upsertRecurringTransaction(t);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:grouped_list/grouped_list.dart';
|
import 'package:grouped_list/grouped_list.dart';
|
||||||
|
import 'package:okane/database/database.dart';
|
||||||
import 'package:okane/ui/state/core.dart';
|
import 'package:okane/ui/state/core.dart';
|
||||||
import 'package:okane/ui/utils.dart';
|
import 'package:okane/ui/utils.dart';
|
||||||
import 'package:okane/ui/widgets/add_template.dart';
|
import 'package:okane/ui/widgets/add_template.dart';
|
||||||
@ -30,7 +31,25 @@ class TemplateListState extends State<TemplateListPage> {
|
|||||||
itemCount: nonRecurringTemplates.length,
|
itemCount: nonRecurringTemplates.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final template = nonRecurringTemplates[index];
|
final template = nonRecurringTemplates[index];
|
||||||
return ListTile(title: Text(template.name));
|
return ListTile(
|
||||||
|
title: Text(template.name),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: Icon(Icons.delete),
|
||||||
|
color: Colors.red,
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await confirm(
|
||||||
|
context,
|
||||||
|
"Remove Template",
|
||||||
|
"Are you sure you want to remove the template '${template.name}'",
|
||||||
|
);
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteTransactionTemplate(template);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: Text("Recurring")),
|
SliverToBoxAdapter(child: Text("Recurring")),
|
||||||
@ -38,31 +57,32 @@ class TemplateListState extends State<TemplateListPage> {
|
|||||||
itemCount: state.recurringTransactions.length,
|
itemCount: state.recurringTransactions.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final template = state.recurringTransactions[index];
|
final template = state.recurringTransactions[index];
|
||||||
return ListTile(title: Text(template.template.value!.name));
|
return ListTile(
|
||||||
|
title: Text(template.template.value!.name),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await confirm(
|
||||||
|
context,
|
||||||
|
"Remove Template",
|
||||||
|
"Are you sure you want to remove the template '${template.template.value!.name}'",
|
||||||
|
);
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteRecurringTransactionTemplate(template);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
/*Padding(
|
|
||||||
padding: EdgeInsets.only(top: 16),
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: state.recurringTransactions.length,
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemBuilder: (ctx, idx) {
|
|
||||||
print(idx);
|
|
||||||
return ListTile(
|
|
||||||
title: Text(
|
|
||||||
state.recurringTransactions[idx].template.value!.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),*/
|
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 16,
|
right: 16,
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
child: FloatingActionButton(
|
child: FloatingActionButton(
|
||||||
child: Icon(Icons.add),
|
|
||||||
onPressed:
|
onPressed:
|
||||||
account == null
|
account == null
|
||||||
? () {}
|
? () {}
|
||||||
@ -80,6 +100,7 @@ class TemplateListState extends State<TemplateListPage> {
|
|||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: Icon(Icons.add),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -83,58 +83,10 @@ class TransactionListState extends State<TransactionListPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
/*Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: 16),
|
|
||||||
child: GroupedListView(
|
|
||||||
elements: state.transactions,
|
|
||||||
reverse: true,
|
|
||||||
groupBy:
|
|
||||||
(Transaction item) => formatDateTime(item.date),
|
|
||||||
groupHeaderBuilder:
|
|
||||||
(item) => Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withAlpha(170),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
child: Text(
|
|
||||||
formatDateTime(item.date),
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
shrinkWrap: true,
|
|
||||||
indexedItemBuilder:
|
|
||||||
(ctx, item, idx) => TransactionCard(
|
|
||||||
transaction: item,
|
|
||||||
onTap: () {
|
|
||||||
GetIt.I.get<CoreCubit>().setActiveTransaction(
|
|
||||||
item,
|
|
||||||
);
|
|
||||||
if (getScreenSize(ctx) == ScreenSize.small) {
|
|
||||||
Navigator.of(
|
|
||||||
context,
|
|
||||||
).pushNamed("/transactions/details");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),*/
|
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 16,
|
right: 16,
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
child: FloatingActionButton(
|
child: FloatingActionButton(
|
||||||
child: Icon(Icons.add),
|
|
||||||
onPressed:
|
onPressed:
|
||||||
account == null
|
account == null
|
||||||
? () {}
|
? () {}
|
||||||
@ -144,7 +96,7 @@ class TransactionListState extends State<TransactionListPage> {
|
|||||||
builder:
|
builder:
|
||||||
(ctx) => AddTransactionWidget(
|
(ctx) => AddTransactionWidget(
|
||||||
activeAccountItem: account,
|
activeAccountItem: account,
|
||||||
onAdd: () {
|
onAdd: (_) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
@ -152,6 +104,7 @@ class TransactionListState extends State<TransactionListPage> {
|
|||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: Icon(Icons.add),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -66,6 +66,7 @@ class CoreCubit extends Cubit<CoreState> {
|
|||||||
_recurringTransactionStreamSubscription = watchRecurringTransactions(
|
_recurringTransactionStreamSubscription = watchRecurringTransactions(
|
||||||
activeAccount!,
|
activeAccount!,
|
||||||
).listen((_) async {
|
).listen((_) async {
|
||||||
|
print("RECURRING UPDATE");
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
recurringTransactions: await getRecurringTransactions(activeAccount!),
|
recurringTransactions: await getRecurringTransactions(activeAccount!),
|
||||||
@ -76,7 +77,6 @@ class CoreCubit extends Cubit<CoreState> {
|
|||||||
_transactionTemplatesStreamSubcription = watchTransactionTemplates(
|
_transactionTemplatesStreamSubcription = watchTransactionTemplates(
|
||||||
activeAccount!,
|
activeAccount!,
|
||||||
).listen((_) async {
|
).listen((_) async {
|
||||||
print("UPDATE");
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
transactionTemplates: await getTransactionTemplates(activeAccount!),
|
transactionTemplates: await getTransactionTemplates(activeAccount!),
|
||||||
|
@ -116,3 +116,29 @@ String formatCurrency(double amount, {bool precise = true}) {
|
|||||||
DateTime monthEnding(DateTime now) {
|
DateTime monthEnding(DateTime now) {
|
||||||
return DateTime(now.year, now.month, 32, 23, 59, 59);
|
return DateTime(now.year, now.month, 32, 23, 59, 59);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> confirm(BuildContext context, String title, String body) async {
|
||||||
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: Text(body),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
child: Text("Delete", style: TextStyle(color: Colors.red)),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(false);
|
||||||
|
},
|
||||||
|
child: Text("Cancel"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return result ?? false;
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import 'package:get_it/get_it.dart';
|
|||||||
import 'package:okane/database/collections/account.dart';
|
import 'package:okane/database/collections/account.dart';
|
||||||
import 'package:okane/database/collections/beneficiary.dart';
|
import 'package:okane/database/collections/beneficiary.dart';
|
||||||
import 'package:okane/database/collections/expense_category.dart';
|
import 'package:okane/database/collections/expense_category.dart';
|
||||||
|
import 'package:okane/database/collections/template.dart';
|
||||||
import 'package:okane/database/collections/transaction.dart';
|
import 'package:okane/database/collections/transaction.dart';
|
||||||
import 'package:okane/database/database.dart';
|
import 'package:okane/database/database.dart';
|
||||||
import 'package:okane/ui/state/core.dart';
|
import 'package:okane/ui/state/core.dart';
|
||||||
@ -12,13 +13,18 @@ import 'package:okane/ui/utils.dart';
|
|||||||
import 'package:okane/ui/widgets/add_expense_category.dart';
|
import 'package:okane/ui/widgets/add_expense_category.dart';
|
||||||
import 'package:searchfield/searchfield.dart';
|
import 'package:searchfield/searchfield.dart';
|
||||||
|
|
||||||
|
typedef AddTransactionCallback = void Function(Transaction);
|
||||||
|
|
||||||
class AddTransactionWidget extends StatefulWidget {
|
class AddTransactionWidget extends StatefulWidget {
|
||||||
final VoidCallback onAdd;
|
final AddTransactionCallback onAdd;
|
||||||
|
|
||||||
final Account activeAccountItem;
|
final Account activeAccountItem;
|
||||||
|
|
||||||
|
final TransactionTemplate? template;
|
||||||
|
|
||||||
const AddTransactionWidget({
|
const AddTransactionWidget({
|
||||||
super.key,
|
super.key,
|
||||||
|
this.template,
|
||||||
required this.activeAccountItem,
|
required this.activeAccountItem,
|
||||||
required this.onAdd,
|
required this.onAdd,
|
||||||
});
|
});
|
||||||
@ -38,7 +44,26 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
|
|||||||
|
|
||||||
TransactionDirection _selectedDirection = TransactionDirection.send;
|
TransactionDirection _selectedDirection = TransactionDirection.send;
|
||||||
|
|
||||||
ExpenseCategory? _expenseCategory = null;
|
ExpenseCategory? _expenseCategory;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
if (widget.template != null) {
|
||||||
|
_selectedDirection =
|
||||||
|
widget.template!.amount > 0
|
||||||
|
? TransactionDirection.receive
|
||||||
|
: TransactionDirection.send;
|
||||||
|
_amountTextController.text = widget.template!.amount.toString();
|
||||||
|
_beneficiaryTextController.text =
|
||||||
|
widget.template!.beneficiary.value!.name;
|
||||||
|
_selectedBeneficiary = SearchFieldListItem(
|
||||||
|
getBeneficiaryName(widget.template!.beneficiary.value!),
|
||||||
|
item: widget.template!.beneficiary.value!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String getBeneficiaryName(Beneficiary item) {
|
String getBeneficiaryName(Beneficiary item) {
|
||||||
return switch (item.type) {
|
return switch (item.type) {
|
||||||
@ -122,7 +147,7 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
|
|||||||
await upsertTransaction(otherTransaction);
|
await upsertTransaction(otherTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.onAdd();
|
widget.onAdd(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
38
test/database/collections/recurrent.dart
Normal file
38
test/database/collections/recurrent.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:okane/database/collections/recurrent.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group("isDue", () {
|
||||||
|
test("null value", () {
|
||||||
|
final t = RecurringTransaction()..lastExecution = null;
|
||||||
|
expect(t.isDue(DateTime.now()), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Date before", () {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final t =
|
||||||
|
RecurringTransaction()
|
||||||
|
..lastExecution = now
|
||||||
|
..days = 30;
|
||||||
|
expect(t.isDue(now.add(Duration(days: 10))), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Date before warning", () {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final t =
|
||||||
|
RecurringTransaction()
|
||||||
|
..lastExecution = now
|
||||||
|
..days = 30;
|
||||||
|
expect(t.isDue(now.add(Duration(days: 20))), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Expired", () {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final t =
|
||||||
|
RecurringTransaction()
|
||||||
|
..lastExecution = now
|
||||||
|
..days = 30;
|
||||||
|
expect(t.isDue(now.add(Duration(days: 31))), true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'package:okane/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
|
||||||
// Build our app and trigger a frame.
|
|
||||||
await tester.pumpWidget(const MyApp());
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
|
||||||
expect(find.text('0'), findsOneWidget);
|
|
||||||
expect(find.text('1'), findsNothing);
|
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
|
||||||
expect(find.text('0'), findsNothing);
|
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user