import 'dart:io'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart'; import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart'; import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_day.dart'; import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart'; import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/order_panel.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_menu.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart'; import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart'; import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:calendar_view/calendar_view.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/text_input.dart'; import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart'; import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; import 'package:table_calendar/table_calendar.dart'; import 'base.dart'; import 'common_operations.dart'; import 'expectation.dart'; import 'package:path/path.dart' as p; import 'mock/mock_file_picker.dart'; extension AppFlowyDatabaseTest on WidgetTester { Future openV020database() async { final context = await initializeAppFlowy(); await tapGoButton(); // expect to see a readme page expectToSeePageName(readme); await tapAddButton(); await tapImportButton(); final testFileNames = ['v020.afdb']; final paths = []; for (final fileName in testFileNames) { // Don't use the p.join to build the path that used in loadString. It // is not working on windows. final str = await rootBundle .loadString("assets/test/workspaces/database/$fileName"); // Write the content to the file. final path = p.join( context.applicationDataDirectory, fileName, ); paths.add(path); File(path).writeAsStringSync(str); } // mock get files await mockPickFilePaths( paths: paths, ); await tapDatabaseRawDataButton(); await openPage('v020'); } Future hoverOnFirstRowOfGrid() async { final findRow = find.byType(GridRow); expect(findRow, findsWidgets); final firstRow = findRow.first; await hoverOnWidget(firstRow); } Future editCell({ required int rowIndex, required FieldType fieldType, required String input, int cellIndex = 0, }) async { final cell = cellFinder(rowIndex, fieldType, cellIndex: cellIndex); expect(cell, findsOneWidget); await enterText(cell, input); await pumpAndSettle(); } /// Finder cellFinder(int rowIndex, FieldType fieldType, {int cellIndex = 0}) { final findRow = find.byType(GridRow, skipOffstage: false); final findCell = finderForFieldType(fieldType); return find .descendant( of: findRow.at(rowIndex), matching: findCell, skipOffstage: false, ) .at(cellIndex); } Future tapCheckboxCellInGrid({ required int rowIndex, }) async { final cell = cellFinder(rowIndex, FieldType.Checkbox); final button = find.descendant( of: cell, matching: find.byType(FlowyIconButton), ); expect(cell, findsOneWidget); await tapButton(button); } Future assertCheckboxCell({ required int rowIndex, required bool isSelected, }) async { final cell = cellFinder(rowIndex, FieldType.Checkbox); var finder = find.byType(CheckboxCellUncheck); if (isSelected) { finder = find.byType(CheckboxCellCheck); } expect( find.descendant( of: cell, matching: finder, ), findsOneWidget, ); } Future tapCellInGrid({ required int rowIndex, required FieldType fieldType, }) async { final cell = cellFinder(rowIndex, fieldType); expect(cell, findsOneWidget); await tapButton(cell, warnIfMissed: false); } /// The [fieldName] must be unique in the grid. Future assertCellContent({ required int rowIndex, required FieldType fieldType, required String content, int cellIndex = 0, }) async { final findCell = cellFinder(rowIndex, fieldType, cellIndex: cellIndex); final findContent = find.descendant( of: findCell, matching: find.text(content), skipOffstage: false, ); final text = find.descendant( of: find.byType(TextField), matching: findContent, skipOffstage: false, ); expect(text, findsOneWidget); } Future assertSingleSelectOption({ required int rowIndex, required String content, }) async { final findCell = cellFinder(rowIndex, FieldType.SingleSelect); if (content.isNotEmpty) { final finder = find.descendant( of: findCell, matching: find.byWidgetPredicate( (widget) => widget is SelectOptionTag && widget.name == content, ), ); expect(finder, findsOneWidget); } } Future assertMultiSelectOption({ required int rowIndex, required List contents, }) async { final findCell = cellFinder(rowIndex, FieldType.MultiSelect); for (final content in contents) { if (content.isNotEmpty) { final finder = find.descendant( of: findCell, matching: find.byWidgetPredicate( (widget) => widget is SelectOptionTag && widget.name == content, ), ); expect(finder, findsOneWidget); } } } Future assertChecklistCellInGrid({ required int rowIndex, required double percent, }) async { final findCell = cellFinder(rowIndex, FieldType.Checklist); final finder = find.descendant( of: findCell, matching: find.byWidgetPredicate( (widget) { if (widget is ChecklistProgressBar) { return widget.percent == percent; } return false; }, ), ); expect(finder, findsOneWidget); } Future assertDateCellInGrid({ required int rowIndex, required FieldType fieldType, required String content, }) async { final findRow = find.byType(GridRow, skipOffstage: false); final findCell = find.descendant( of: findRow.at(rowIndex), matching: find.byWidgetPredicate( (widget) => widget is GridDateCell && widget.fieldType == fieldType, ), skipOffstage: false, ); final dateCellText = find.descendant( of: findCell, matching: find.byType(GridDateCellText), ); final text = find.descendant( of: dateCellText, matching: find.byWidgetPredicate( (widget) { if (widget is FlowyText) { return widget.text == content; } return false; }, ), skipOffstage: false, ); expect(text, findsOneWidget); } Future selectDay({ required int content, }) async { final findCalendar = find.byType(TableCalendar); final findDay = find.text(content.toString()); final finder = find.descendant( of: findCalendar, matching: findDay, ); // if the day is very near the beginning or the end of the month, // it may overlap with the same day in the next or previous month, // respectively because it was spilling over. This will lead to 2 // widgets being found and thus cannot be tapped correctly. if (content < 15) { // e.g., Jan 2 instead of Feb 2 await tapButton(finder.first); } else { // e.g. Jun 28 instead of May 28 await tapButton(finder.last); } } Future toggleIncludeTime() async { final findDateEditor = find.byType(DateCellEditor); final findToggle = find.byType(Toggle); final finder = find.descendant( of: findDateEditor, matching: findToggle, ); await tapButton(finder); } Future changeDateFormat() async { final findDateEditor = find.byType(DateCellEditor); final findDateTimeOptionButton = find.byType(DateTypeOptionButton); final finder = find.descendant( of: findDateEditor, matching: findDateTimeOptionButton, ); await tapButton(finder); final findDateFormatButton = find.byType(DateFormatButton); await tapButton(findDateFormatButton); final findNewDateFormat = find.text("Day/Month/Year"); await tapButton(findNewDateFormat); } Future changeTimeFormat() async { final findDateEditor = find.byType(DateCellEditor); final findDateTimeOptionButton = find.byType(DateTypeOptionButton); final finder = find.descendant( of: findDateEditor, matching: findDateTimeOptionButton, ); await tapButton(finder); final findDateFormatButton = find.byType(TimeFormatButton); await tapButton(findDateFormatButton); final findNewDateFormat = find.text("12 hour"); await tapButton(findNewDateFormat); } Future tapSelectOptionCellInGrid({ required int rowIndex, required FieldType fieldType, }) async { assert( fieldType == FieldType.SingleSelect || fieldType == FieldType.MultiSelect, ); final findRow = find.byType(GridRow); final findCell = finderForFieldType(fieldType); final cell = find.descendant( of: findRow.at(rowIndex), matching: findCell, ); await tapButton(cell); } /// The [SelectOptionCellEditor] must be opened first. Future createOption({ required String name, }) async { final findEditor = find.byType(SelectOptionCellEditor); expect(findEditor, findsOneWidget); final findTextField = find.byType(SelectOptionTextField); expect(findTextField, findsOneWidget); await enterText(findTextField, name); await pump(); await testTextInput.receiveAction(TextInputAction.done); await pumpAndSettle(); } Future selectOption({ required String name, }) async { final option = find.byWidgetPredicate( (widget) => widget is SelectOptionTagCell && widget.option.name == name, ); await tapButton(option); } Future findSelectOptionWithNameInGrid({ required int rowIndex, required String name, }) async { final findRow = find.byType(GridRow); final option = find.byWidgetPredicate( (widget) => widget is SelectOptionTag && widget.name == name, ); final cell = find.descendant( of: findRow.at(rowIndex), matching: option, ); expect(cell, findsOneWidget); } Future assertNumberOfSelectedOptionsInGrid({ required int rowIndex, required Matcher matcher, }) async { final findRow = find.byType(GridRow); final options = find.byWidgetPredicate( (widget) => widget is SelectOptionTag, ); final cell = find.descendant( of: findRow.at(rowIndex), matching: options, ); expect(cell, matcher); } Future openFirstRowDetailPage() async { await hoverOnFirstRowOfGrid(); final expandButton = find.byType(PrimaryCellAccessory); expect(expandButton, findsOneWidget); await tapButton(expandButton); } void assertRowDetailPageOpened() async { final findRowDetailPage = find.byType(RowDetailPage); expect(findRowDetailPage, findsOneWidget); } Future dismissRowDetailPage() async { // use tap empty area instead of clicking ESC to dismiss the row detail page // sometimes, the ESC key is not working. await simulateKeyEvent(LogicalKeyboardKey.escape); await pumpAndSettle(); final findRowDetailPage = find.byType(RowDetailPage); if (findRowDetailPage.evaluate().isNotEmpty) { await tapAt(const Offset(0, 0)); await pumpAndSettle(); } } Future editTitleInRowDetailPage(String title) async { final titleField = find.byType(GridTextCell); await enterText(titleField, title); await pumpAndSettle(); } Future hoverRowBanner() async { final banner = find.byType(RowBanner); expect(banner, findsOneWidget); await startGesture( getTopLeft(banner), kind: PointerDeviceKind.mouse, ); await pumpAndSettle(); } Future openEmojiPicker() async { await tapButton(find.byType(EmojiPickerButton)); await tapButton(find.byType(EmojiSelectionMenu)); } Future tapDateCellInRowDetailPage() async { final findDateCell = find.byType(GridDateCell); await tapButton(findDateCell); } Future duplicateRowInRowDetailPage() async { final duplicateButton = find.byType(RowDetailPageDuplicateButton); await tapButton(duplicateButton); } Future deleteRowInRowDetailPage() async { final deleteButton = find.byType(RowDetailPageDeleteButton); await tapButton(deleteButton); } Future scrollGridByOffset(Offset offset) async { await drag(find.byType(GridPage), offset); await pumpAndSettle(); } Future scrollRowDetailByOffset(Offset offset) async { await drag(find.byType(RowDetailPage), offset); await pumpAndSettle(); } Future scrollToRight(Finder find) async { final size = getSize(find); await drag(find, Offset(-size.width, 0)); await pumpAndSettle(const Duration(milliseconds: 500)); } Future tapNewPropertyButton() async { await tapButtonWithName(LocaleKeys.grid_field_newProperty.tr()); await pumpAndSettle(); } Future tapGridFieldWithName(String name) async { final field = find.byWidgetPredicate( (widget) => widget is FieldCellButton && widget.field.name == name, ); await tapButton(field); await pumpAndSettle(); } /// Should call [tapGridFieldWithName] first. Future tapEditPropertyButton() async { await tapButtonWithName(LocaleKeys.grid_field_editProperty.tr()); await pumpAndSettle(const Duration(milliseconds: 200)); } /// Should call [tapGridFieldWithName] first. Future tapDeletePropertyButton() async { final field = find.byWidgetPredicate( (widget) => widget is FieldActionCell && widget.action == FieldAction.delete, ); await tapButton(field); } /// Should call [tapGridFieldWithName] first. Future tapDialogOkButton() async { final field = find.byWidgetPredicate( (widget) => widget is PrimaryTextButton && widget.label == LocaleKeys.button_OK.tr(), ); await tapButton(field); } /// Should call [tapGridFieldWithName] first. Future tapDuplicatePropertyButton() async { final field = find.byWidgetPredicate( (widget) => widget is FieldActionCell && widget.action == FieldAction.duplicate, ); await tapButton(field); } /// Should call [tapGridFieldWithName] first. Future tapHidePropertyButton() async { final field = find.byWidgetPredicate( (widget) => widget is FieldActionCell && widget.action == FieldAction.hide, ); await tapButton(field); } Future tapRowDetailPageCreatePropertyButton() async { await tapButton(find.byType(CreateRowFieldButton)); } Future tapRowDetailPageDeleteRowButton() async { await tapButton(find.byType(RowDetailPageDeleteButton)); } Future tapRowDetailPageDuplicateRowButton() async { await tapButton(find.byType(RowDetailPageDuplicateButton)); } Future tapTypeOptionButton() async { await tapButton(find.byType(SwitchFieldButton)); } Future tapEscButton() async { await sendKeyEvent(LogicalKeyboardKey.escape); } /// Must call [tapTypeOptionButton] first. Future selectFieldType(FieldType fieldType) async { final fieldTypeCell = find.byType(FieldTypeCell); final fieldTypeButton = find.descendant( of: fieldTypeCell, matching: find.byWidgetPredicate( (widget) => widget is FlowyText && widget.text == fieldType.title(), ), ); await tapButton(fieldTypeButton); } /// Each field has its own cell, so we can find the corresponding cell by /// the field type after create a new field. Future findCellByFieldType(FieldType fieldType) async { final finder = finderForFieldType(fieldType); expect(finder, findsWidgets); } Future assertNumberOfFieldsInGridPage(int num) async { expect(find.byType(GridFieldCell), findsNWidgets(num)); } Future assertNumberOfRowsInGridPage(int num) async { expect( find.byType(GridRow, skipOffstage: false), findsNWidgets(num), ); } Future assertDocumentExistInRowDetailPage() async { expect(find.byType(RowDocument), findsOneWidget); } /// Check the field type of the [FieldCellButton] is the same as the name. Future assertFieldTypeWithFieldName( String name, FieldType fieldType, ) async { final field = find.byWidgetPredicate( (widget) => widget is FieldCellButton && widget.field.fieldType == fieldType && widget.field.name == name, ); expect(field, findsOneWidget); } Future findFieldWithName(String name) async { final field = find.byWidgetPredicate( (widget) => widget is FieldCellButton && widget.field.name == name, ); expect(field, findsOneWidget); } Future noFieldWithName(String name) async { final field = find.byWidgetPredicate( (widget) => widget is FieldCellButton && widget.field.name == name, ); expect(field, findsNothing); } Future renameField(String newName) async { final textField = find.byType(FieldNameTextField); expect(textField, findsOneWidget); await enterText(textField, newName); await pumpAndSettle(); } Future dismissFieldEditor() async { await sendKeyEvent(LogicalKeyboardKey.escape); await pumpAndSettle(const Duration(milliseconds: 200)); } Future findFieldEditor(dynamic matcher) async { final finder = find.byType(FieldEditor); expect(finder, matcher); } Future findDateEditor(dynamic matcher) async { final finder = find.byType(DateCellEditor); expect(finder, matcher); } Future findSelectOptionEditor(dynamic matcher) async { final finder = find.byType(SelectOptionCellEditor); expect(finder, matcher); } Future dismissCellEditor() async { await sendKeyEvent(LogicalKeyboardKey.escape); await pumpAndSettle(); } Future tapCreateRowButtonInGrid() async { await tapButton(find.byType(GridAddRowButton)); } Future tapCreateRowButtonInRowMenuOfGrid() async { await tapButton(find.byType(InsertRowButton)); } Future tapRowMenuButtonInGrid() async { await tapButton(find.byType(RowMenuButton)); } /// Should call [tapRowMenuButtonInGrid] first. Future tapDeleteOnRowMenu() async { await tapButtonWithName(LocaleKeys.grid_row_delete.tr()); } Future assertRowCountInGridPage(int num) async { final text = find.byWidgetPredicate( (widget) => widget is FlowyText && widget.text == rowCountString(num), ); expect(text, findsOneWidget); } Future createField(FieldType fieldType, String name) async { await scrollToRight(find.byType(GridPage)); await tapNewPropertyButton(); await renameField(name); await tapTypeOptionButton(); await selectFieldType(fieldType); await dismissFieldEditor(); } Future tapDatabaseSettingButton() async { await tapButton(find.byType(SettingButton)); } Future tapDatabaseFilterButton() async { await tapButton(find.byType(FilterButton)); } Future tapDatabaseSortButton() async { await tapButton(find.byType(SortButton)); } Future tapCreateFilterByFieldType( FieldType fieldType, String title, ) async { final findFilter = find.byWidgetPredicate( (widget) => widget is GridFilterPropertyCell && widget.fieldInfo.fieldType == fieldType && widget.fieldInfo.name == title, ); await tapButton(findFilter); } Future tapFilterButtonInGrid(String filterName) async { final findFilter = find.byType(FilterMenuItem); final button = find.descendant( of: findFilter, matching: find.text(filterName), ); await tapButton(button); } Future tapCreateSortByFieldType( FieldType fieldType, String title, ) async { final findSort = find.byWidgetPredicate( (widget) => widget is GridSortPropertyCell && widget.fieldInfo.fieldType == fieldType && widget.fieldInfo.name == title, ); await tapButton(findSort); } // Must call [tapSortMenuInSettingBar] first. Future tapCreateSortByFieldTypeInSortMenu( FieldType fieldType, String title, ) async { await tapButton(find.byType(DatabaseAddSortButton)); final findSort = find.byWidgetPredicate( (widget) => widget is GridSortPropertyCell && widget.fieldInfo.fieldType == fieldType && widget.fieldInfo.name == title, ); await tapButton(findSort); await pumpAndSettle(); } Future tapSortMenuInSettingBar() async { await tapButton(find.byType(SortMenu)); await pumpAndSettle(); } /// Must call [tapSortMenuInSettingBar] first. Future tapSortButtonByName(String name) async { final findSortItem = find.byWidgetPredicate( (widget) => widget is DatabaseSortItem && widget.sortInfo.fieldInfo.name == name, ); await tapButton(findSortItem); } /// Must call [tapSortButtonByName] first. Future tapSortByDescending() async { await tapButton( find.descendant( of: find.byType(OrderPannelItem), matching: find.byWidgetPredicate( (widget) => widget is FlowyText && widget.text == LocaleKeys.grid_sort_descending.tr(), ), ), ); await sendKeyEvent(LogicalKeyboardKey.escape); await pumpAndSettle(); } /// Must call [tapSortMenuInSettingBar] first. Future tapAllSortButton() async { await tapButton(find.byType(DatabaseDeleteSortButton)); } Future scrollOptionFilterListByOffset(Offset offset) async { await drag(find.byType(SelectOptionFilterEditor), offset); await pumpAndSettle(); } Future enterTextInTextFilter(String text) async { final findEditor = find.byType(TextFilterEditor); final findTextField = find.descendant( of: findEditor, matching: find.byType(FlowyTextField), ); await enterText(findTextField, text); await pumpAndSettle(const Duration(milliseconds: 300)); } Future tapDisclosureButtonInFinder(Finder finder) async { final findDisclosure = find.descendant( of: finder, matching: find.byType(DisclosureButton), ); await tapButton(findDisclosure); } /// must call [tapDisclosureButtonInFinder] first. Future tapDeleteFilterButtonInGrid() async { await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr())); } Future tapCheckboxFilterButtonInGrid() async { await tapButton(find.byType(CheckboxFilterConditionList)); } Future tapChecklistFilterButtonInGrid() async { await tapButton(find.byType(ChecklistFilterConditionList)); } /// The [SelectOptionFilterList] must show up first. Future tapOptionFilterWithName(String name) async { final findCell = find.descendant( of: find.byType(SelectOptionFilterList), matching: find.byWidgetPredicate( (widget) => widget is SelectOptionFilterCell && widget.option.name == name, skipOffstage: false, ), skipOffstage: false, ); expect(findCell, findsOneWidget); await tapButton(findCell, warnIfMissed: false); } Future tapCheckedButtonOnCheckboxFilter() async { final button = find.descendant( of: find.byType(HoverButton), matching: find.text(LocaleKeys.grid_checkboxFilter_isChecked.tr()), ); await tapButton(button); } Future tapUnCheckedButtonOnCheckboxFilter() async { final button = find.descendant( of: find.byType(HoverButton), matching: find.text(LocaleKeys.grid_checkboxFilter_isUnchecked.tr()), ); await tapButton(button); } Future tapCompletedButtonOnChecklistFilter() async { final button = find.descendant( of: find.byType(HoverButton), matching: find.text(LocaleKeys.grid_checklistFilter_isComplete.tr()), ); await tapButton(button); } Future tapUnCompletedButtonOnChecklistFilter() async { final button = find.descendant( of: find.byType(HoverButton), matching: find.text(LocaleKeys.grid_checklistFilter_isIncomplted.tr()), ); await tapButton(button); } /// Should call [tapDatabaseSettingButton] first. Future tapDatabaseLayoutButton() async { final findSettingItem = find.byType(DatabaseSettingItem); final findLayoutButton = find.byWidgetPredicate( (widget) => widget is FlowyText && widget.text == DatabaseSettingAction.showLayout.title(), ); final button = find.descendant( of: findSettingItem, matching: findLayoutButton, ); await tapButton(button); } Future tapCalendarLayoutSettingButton() async { final findSettingItem = find.byType(DatabaseSettingItem); final findLayoutButton = find.byWidgetPredicate( (widget) => widget is FlowyText && widget.text == DatabaseSettingAction.showCalendarLayout.title(), ); final button = find.descendant( of: findSettingItem, matching: findLayoutButton, ); await tapButton(button); } Future tapFirstDayOfWeek() async { await tapButton(find.byType(FirstDayOfWeek)); } Future tapFirstDayOfWeekStartFromSunday() async { final finder = find.byWidgetPredicate( (widget) => widget is StartFromButton && widget.dayIndex == 0, ); await tapButton(finder); } Future tapFirstDayOfWeekStartFromMonday() async { final finder = find.byWidgetPredicate( (widget) => widget is StartFromButton && widget.dayIndex == 1, ); await tapButton(finder); // Dismiss the popover overlay in cause of obscure the tapButton // in the next test case. await sendKeyEvent(LogicalKeyboardKey.escape); await pumpAndSettle(const Duration(milliseconds: 200)); } void assertFirstDayOfWeekStartFromMonday() { final finder = find.byWidgetPredicate( (widget) => widget is StartFromButton && widget.dayIndex == 1 && widget.isSelected == true, ); expect(finder, findsOneWidget); } void assertFirstDayOfWeekStartFromSunday() { final finder = find.byWidgetPredicate( (widget) => widget is StartFromButton && widget.dayIndex == 0 && widget.isSelected == true, ); expect(finder, findsOneWidget); } Future scrollToToday() async { final todayCell = find.byWidgetPredicate( (widget) => widget is CalendarDayCard && widget.isToday, ); final scrollable = find .descendant( of: find.byType(MonthView), matching: find.byWidgetPredicate( (widget) => widget is Scrollable && widget.axis == Axis.vertical, ), ) .first; await scrollUntilVisible( todayCell, 300, scrollable: scrollable, ); await pumpAndSettle(const Duration(milliseconds: 300)); } Future hoverOnTodayCalendarCell() async { final todayCell = find.byWidgetPredicate( (widget) => widget is CalendarDayCard && widget.isToday, ); await hoverOnWidget(todayCell); } Future tapAddCalendarEventButton() async { final findFlowyButton = find.byType(FlowyIconButton); final findNewEventButton = find.byType(NewEventButton); final button = find.descendant( of: findNewEventButton, matching: findFlowyButton, ); await tapButton(button); } /// Checks for a certain number of events. Parameters [date] and [title] can /// also be provided to restrict the scope of the search void assertNumberOfEventsInCalendar(int number, {String? title}) { Finder findEvents = find.byType(EventCard); if (title != null) { findEvents = find.descendant(of: findEvents, matching: find.text(title)); } expect(findEvents, findsNWidgets(number)); } void assertNumberOfEventsOnSpecificDay( int number, DateTime date, { String? title, }) { final findDayCell = find.byWidgetPredicate( (widget) => widget is CalendarDayCard && isSameDay( widget.date, date, ), ); Finder findEvents = find.descendant( of: findDayCell, matching: find.byType(EventCard), ); if (title != null) { findEvents = find.descendant(of: findEvents, matching: find.text(title)); } expect(findEvents, findsNWidgets(number)); } Future doubleClickCalendarCell(DateTime date) async { final todayCell = find.byWidgetPredicate( (widget) => widget is CalendarDayCard && isSameDay(date, widget.date), ); final location = getTopLeft(todayCell).translate(10, 10); await doubleTapAt(location); } Future openCalendarEvent({required index, DateTime? date}) async { final findDayCell = find.byWidgetPredicate( (widget) => widget is CalendarDayCard && isSameDay(widget.date, date ?? DateTime.now()), ); final cards = find.descendant( of: findDayCell, matching: find.byType(EventCard), ); await tapButton(cards.at(index)); } Future dragDropRescheduleCalendarEvent(DateTime startDate) async { final findEventCard = find.byType(EventCard); await drag(findEventCard.first, const Offset(0, 300)); await pumpAndSettle(); } Future tapCreateLinkedDatabaseViewButton(AddButtonAction action) async { final findAddButton = find.byType(AddDatabaseViewButton); await tapButton(findAddButton); final findCreateButton = find.byWidgetPredicate( (widget) => widget is TarBarAddButtonActionCell && widget.action == action, ); await tapButton(findCreateButton); } Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) { return find.byWidgetPredicate( (widget) => widget is TabBarItemButton && widget.view.layout == layout, ); } Finder findTabBarLinkViewByViewName(String name) { return find.byWidgetPredicate( (widget) => widget is TabBarItemButton && widget.view.name == name, ); } Future renameLinkedView(Finder linkedView, String name) async { await tap(linkedView, buttons: kSecondaryButton); await pumpAndSettle(); await tapButton( find.byWidgetPredicate( (widget) => widget is ActionCellWidget && widget.action == TabBarViewAction.rename, ), ); await enterText( find.descendant( of: find.byType(FlowyFormTextInput), matching: find.byType(TextFormField), ), name, ); final field = find.byWidgetPredicate( (widget) => widget is PrimaryTextButton && widget.label == LocaleKeys.button_OK.tr(), ); await tapButton(field); } Future deleteDatebaseView(Finder linkedView) async { await tap(linkedView, buttons: kSecondaryButton); await pumpAndSettle(); await tapButton( find.byWidgetPredicate( (widget) => widget is ActionCellWidget && widget.action == TabBarViewAction.delete, ), ); final okButton = find.byWidgetPredicate( (widget) => widget is PrimaryTextButton && widget.label == LocaleKeys.button_OK.tr(), ); await tapButton(okButton); } Future assertCurrentDatabaseTagIs(DatabaseLayoutPB layout) async { switch (layout) { case DatabaseLayoutPB.Board: expect(find.byType(BoardPage), findsOneWidget); break; case DatabaseLayoutPB.Calendar: expect(find.byType(CalendarPage), findsOneWidget); break; case DatabaseLayoutPB.Grid: expect(find.byType(GridPage), findsOneWidget); break; default: throw Exception('Unknown database layout type: $layout'); } } Future selectDatabaseLayoutType(DatabaseLayoutPB layout) async { final findLayoutCell = find.byType(DatabaseViewLayoutCell); final findText = find.byWidgetPredicate( (widget) => widget is FlowyText && widget.text == layout.layoutName(), ); final button = find.descendant( of: findLayoutCell, matching: findText, ); await tapButton(button); } Future assertCurrentDatabaseLayoutType(DatabaseLayoutPB layout) async { expect(finderForDatabaseLayoutType(layout), findsOneWidget); } Future tapDatabaseRawDataButton() async { await tapButtonWithName(LocaleKeys.importPanel_database.tr()); } } Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) { switch (layout) { case DatabaseLayoutPB.Board: return find.byType(BoardPage); case DatabaseLayoutPB.Calendar: return find.byType(CalendarPage); case DatabaseLayoutPB.Grid: return find.byType(GridPage); default: throw Exception('Unknown database layout type: $layout'); } } Finder finderForFieldType(FieldType fieldType) { switch (fieldType) { case FieldType.Checkbox: return find.byType(GridCheckboxCell, skipOffstage: false); case FieldType.DateTime: return find.byType(GridDateCell, skipOffstage: false); case FieldType.LastEditedTime: case FieldType.CreatedTime: return find.byType(GridDateCell, skipOffstage: false); case FieldType.SingleSelect: return find.byType(GridSingleSelectCell, skipOffstage: false); case FieldType.MultiSelect: return find.byType(GridMultiSelectCell, skipOffstage: false); case FieldType.Checklist: return find.byType(GridChecklistCell, skipOffstage: false); case FieldType.Number: return find.byType(GridNumberCell, skipOffstage: false); case FieldType.RichText: return find.byType(GridTextCell, skipOffstage: false); case FieldType.URL: return find.byType(GridURLCell, skipOffstage: false); default: throw Exception('Unknown field type: $fieldType'); } }