| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482 | import 'dart:io';import 'package:appflowy/generated/flowy_svgs.g.dart';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_event_card.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/grid_page.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/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_cell_action_sheet.dart';import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.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/header/type_option/date.dart';import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/timestamp.dart';import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.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/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_header.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/field/grid_property.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/cells/checklist_cell/checklist_cell_editor.dart';import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.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/timestamp_cell/timestamp_cell.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/database_view/widgets/row/row_document.dart';import 'package:appflowy/plugins/database_view/widgets/row/row_property.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/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.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/field_entities.pbenum.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:path/path.dart' as p;import 'package:table_calendar/table_calendar.dart';import 'base.dart';import 'common_operations.dart';import 'expectation.dart';import 'mock/mock_file_picker.dart';extension AppFlowyDatabaseTest on WidgetTester {  Future<void> openV020database() async {    final context = await initializeAppFlowy();    await tapGoButton();    // expect to see a readme page    expectToSeePageName(gettingStarted);    await tapAddViewButton();    await tapImportButton();    final testFileNames = ['v020.afdb'];    final paths = <String>[];    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 pumpAndSettle();    await openPage('v020', layout: ViewLayoutPB.Grid);  }  Future<void> hoverOnFirstRowOfGrid() async {    final findRow = find.byType(GridRow);    expect(findRow, findsWidgets);    final firstRow = findRow.first;    await hoverOnWidget(firstRow);  }  Future<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> assertMultiSelectOption({    required int rowIndex,    required List<String> 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);      }    }  }  /// null percent means no progress bar should be found  Future<void> assertChecklistCellInGrid({    required int rowIndex,    required double? percent,  }) async {    final findCell = cellFinder(rowIndex, FieldType.Checklist);    if (percent == null) {      final finder = find.descendant(        of: findCell,        matching: find.byType(ChecklistProgressBar),      );      expect(finder, findsNothing);    } else {      final finder = find.descendant(        of: findCell,        matching: find.byWidgetPredicate(          (widget) {            if (widget is ChecklistProgressBar) {              return widget.percent == percent;            }            return false;          },        ),      );      expect(finder, findsOneWidget);    }  }  Future<void> assertDateCellInGrid({    required int rowIndex,    required String content,  }) async {    final findRow = find.byType(GridRow, skipOffstage: false);    final findCell = find.descendant(      of: findRow.at(rowIndex),      matching: find.byType(GridDateCell),      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<void> 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<void> toggleIncludeTime() async {    final findDateEditor = find.byType(IncludeTimeButton);    final findToggle = find.byType(Toggle);    final finder = find.descendant(      of: findDateEditor,      matching: findToggle,    );    await tapButton(finder);  }  Future<void> toggleDateRange() async {    final findDateEditor = find.byType(EndTimeButton);    final findToggle = find.byType(Toggle);    final finder = find.descendant(      of: findDateEditor,      matching: findToggle,    );    await tapButton(finder);  }  Future<void> 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<void> 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<void> clearDate() async {    final findDateEditor = find.byType(DateCellEditor);    final findClearButton = find.byType(ClearDateButton);    final finder = find.descendant(      of: findDateEditor,      matching: findClearButton,    );    await tapButton(finder);  }  Future<void> 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<void> 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<void> selectOption({    required String name,  }) async {    final option = find.byWidgetPredicate(      (widget) => widget is SelectOptionTagCell && widget.option.name == name,    );    await tapButton(option);  }  Future<void> 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<void> 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<void> tapChecklistCellInGrid({required int rowIndex}) async {    final findRow = find.byType(GridRow);    final findCell = finderForFieldType(FieldType.Checklist);    final cell = find.descendant(      of: findRow.at(rowIndex),      matching: findCell,    );    await tapButton(cell);  }  void assertChecklistEditorVisible({required bool visible}) {    final editor = find.byType(GridChecklistCellEditor);    if (visible) {      expect(editor, findsOneWidget);    } else {      expect(editor, findsNothing);    }  }  void assertNewCheckListTaskEditorVisible({required bool visible}) {    final editor = find.byType(NewTaskItem);    if (visible) {      expect(editor, findsOneWidget);    } else {      expect(editor, findsNothing);    }  }  Future<void> createNewChecklistTask({    required String name,    enter = false,    button = false,  }) async {    assert(!(enter && button));    final textField = find.descendant(      of: find.byType(NewTaskItem),      matching: find.byType(TextField),    );    await enterText(textField, name);    await pumpAndSettle(const Duration(milliseconds: 300));    if (enter) {      await testTextInput.receiveAction(TextInputAction.done);      await pumpAndSettle(const Duration(milliseconds: 300));    } else {      await tapButton(        find.descendant(          of: find.byType(NewTaskItem),          matching: find.byType(FlowyTextButton),        ),      );    }  }  void assertChecklistTaskInEditor({    required int index,    required String name,    required bool isChecked,  }) {    final task = find.byType(ChecklistItem).at(index);    final widget = this.widget<ChecklistItem>(task);    assert(      widget.option.data.name == name && widget.option.isSelected == isChecked,    );  }  Future<void> renameChecklistTask({    required int index,    required String name,  }) async {    final textField = find        .descendant(          of: find.byType(ChecklistItem),          matching: find.byType(TextField),        )        .at(index);    await enterText(textField, name);    await testTextInput.receiveAction(TextInputAction.done);    await pumpAndSettle(const Duration(milliseconds: 300));  }  Future<void> tapChecklistNewTaskButton() async {    await tapButton(find.byType(ChecklistNewTaskButton));  }  Future<void> checkChecklistTask({required int index}) async {    final button = find.descendant(      of: find.byType(ChecklistItem).at(index),      matching: find.byWidgetPredicate(        (widget) => widget is FlowySvg && widget.svg == FlowySvgs.uncheck_s,      ),    );    await tapButton(button);  }  Future<void> deleteChecklistTask({required int index}) async {    final task = find.byType(ChecklistItem).at(index);    await startGesture(getCenter(task), kind: PointerDeviceKind.mouse);    await pumpAndSettle();    final button = find.byWidgetPredicate(      (widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s,    );    await tapButton(button);  }  Future<void> 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<void> 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<void> editTitleInRowDetailPage(String title) async {    final titleField = find.byType(GridTextCell);    await enterText(titleField, title);    await pumpAndSettle();  }  Future<void> hoverRowBanner() async {    final banner = find.byType(RowBanner);    expect(banner, findsOneWidget);    await startGesture(      getCenter(banner),      kind: PointerDeviceKind.mouse,    );    await pumpAndSettle();  }  Future<void> openEmojiPicker() async {    await tapButton(find.byType(EmojiPickerButton));    await tapButton(find.byType(EmojiSelectionMenu));  }  Future<void> tapDateCellInRowDetailPage() async {    final findDateCell = find.byType(GridDateCell);    await tapButton(findDateCell);  }  Future<void> tapGridFieldWithNameInRowDetailPage(String name) async {    final fields = find.byWidgetPredicate(      (widget) => widget is FieldCellButton && widget.field.name == name,    );    final field = find.descendant(      of: find.byType(RowDetailPage),      matching: fields,    );    await tapButton(field);    await pumpAndSettle();  }  Future<void> duplicateRowInRowDetailPage() async {    final duplicateButton = find.byType(RowDetailPageDuplicateButton);    await tapButton(duplicateButton);  }  Future<void> deleteRowInRowDetailPage() async {    final deleteButton = find.byType(RowDetailPageDeleteButton);    await tapButton(deleteButton);  }  Future<TestGesture> hoverOnFieldInRowDetail({required int index}) async {    final fieldButtons = find.byType(FieldCellButton);    final button = find        .descendant(of: find.byType(RowDetailPage), matching: fieldButtons)        .at(index);    return startGesture(      getCenter(button),      kind: PointerDeviceKind.mouse,    );  }  Future<void> reorderFieldInRowDetail({required double offset}) async {    final thumb = find        .byWidgetPredicate(          (widget) => widget is ReorderableDragStartListener && widget.enabled,        )        .first;    await drag(      thumb,      Offset(0, offset),      kind: PointerDeviceKind.mouse,    );    await pumpAndSettle();  }  Future<void> scrollGridByOffset(Offset offset) async {    await drag(find.byType(GridPage), offset);    await pumpAndSettle();  }  Future<void> scrollRowDetailByOffset(Offset offset) async {    await drag(find.byType(RowDetailPage), offset);    await pumpAndSettle();  }  Future<void> scrollToRight(Finder find) async {    final size = getSize(find);    await drag(find, Offset(-size.width, 0));    await pumpAndSettle(const Duration(milliseconds: 500));  }  Future<void> tapNewPropertyButton() async {    await tapButtonWithName(LocaleKeys.grid_field_newProperty.tr());    await pumpAndSettle();  }  Future<void> 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<void> tapEditPropertyButton() async {    await tapButtonWithName(LocaleKeys.grid_field_editProperty.tr());    await pumpAndSettle(const Duration(milliseconds: 200));  }  /// Should call [tapGridFieldWithName] first.  Future<void> tapDeletePropertyButton() async {    final field = find.byWidgetPredicate(      (widget) =>          widget is FieldActionCell && widget.action == FieldAction.delete,    );    await tapButton(field);  }  /// Should call [tapGridFieldWithName] first.  Future<void> tapDialogOkButton() async {    final field = find.byWidgetPredicate(      (widget) =>          widget is PrimaryTextButton &&          widget.label == LocaleKeys.button_OK.tr(),    );    await tapButton(field);  }  /// Should call [tapGridFieldWithName] first.  Future<void> tapDuplicatePropertyButton() async {    final field = find.byWidgetPredicate(      (widget) =>          widget is FieldActionCell && widget.action == FieldAction.duplicate,    );    await tapButton(field);  }  /// Should call [tapGridFieldWithName] first.  Future<void> tapHidePropertyButton() async {    final field = find.byWidgetPredicate(      (widget) =>          widget is FieldActionCell && widget.action == FieldAction.hide,    );    await tapButton(field);  }  Future<void> tapHidePropertyButtonInFieldEditor() async {    final button = find.byType(HideFieldButton);    await tapButton(button);  }  Future<void> tapRowDetailPageRowActionButton() async {    await tapButton(find.byType(RowActionButton));  }  Future<void> tapRowDetailPageCreatePropertyButton() async {    await tapButton(find.byType(CreateRowFieldButton));  }  Future<void> tapRowDetailPageDeleteRowButton() async {    await tapButton(find.byType(RowDetailPageDeleteButton));  }  Future<void> tapRowDetailPageDuplicateRowButton() async {    await tapButton(find.byType(RowDetailPageDuplicateButton));  }  Future<void> tapTypeOptionButton() async {    await tapButton(find.byType(SwitchFieldButton));  }  Future<void> tapEscButton() async {    await sendKeyEvent(LogicalKeyboardKey.escape);  }  /// Must call [tapTypeOptionButton] first.  Future<void> 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<void> findCellByFieldType(FieldType fieldType) async {    final finder = finderForFieldType(fieldType);    expect(finder, findsWidgets);  }  Future<void> assertNumberOfFieldsInGridPage(int num) async {    expect(find.byType(GridFieldCell), findsNWidgets(num));  }  Future<void> assertNumberOfRowsInGridPage(int num) async {    expect(      find.byType(GridRow, skipOffstage: false),      findsNWidgets(num),    );  }  Future<void> assertDocumentExistInRowDetailPage() async {    expect(find.byType(RowDocument), findsOneWidget);  }  /// Check the field type of the [FieldCellButton] is the same as the name.  Future<void> 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);  }  void assertFirstFieldInRowDetailByType(FieldType fieldType) {    final firstField = find        .descendant(          of: find.byType(RowDetailPage),          matching: find.byType(FieldCellButton),        )        .first;    final widget = this.widget<FieldCellButton>(firstField);    expect(widget.field.fieldType, fieldType);  }  Future<void> findFieldWithName(String name) async {    final field = find.byWidgetPredicate(      (widget) => widget is FieldCellButton && widget.field.name == name,    );    expect(field, findsOneWidget);  }  Future<void> noFieldWithName(String name) async {    final field = find.byWidgetPredicate(      (widget) => widget is FieldCellButton && widget.field.name == name,    );    expect(field, findsNothing);  }  Future<void> renameField(String newName) async {    final textField = find.byType(FieldNameTextField);    expect(textField, findsOneWidget);    await enterText(textField, newName);    await pumpAndSettle();  }  Future<void> dismissFieldEditor() async {    await sendKeyEvent(LogicalKeyboardKey.escape);    await pumpAndSettle(const Duration(milliseconds: 200));  }  Future<void> findFieldEditor(dynamic matcher) async {    final finder = find.byType(FieldEditor);    expect(finder, matcher);  }  Future<void> findDateEditor(dynamic matcher) async {    final finder = find.byType(DateCellEditor);    expect(finder, matcher);  }  Future<void> findSelectOptionEditor(dynamic matcher) async {    final finder = find.byType(SelectOptionCellEditor);    expect(finder, matcher);  }  Future<void> dismissCellEditor() async {    await sendKeyEvent(LogicalKeyboardKey.escape);    await pumpAndSettle();  }  Future<void> tapCreateRowButtonInGrid() async {    await tapButton(find.byType(GridAddRowButton));  }  Future<void> tapCreateRowButtonInRowMenuOfGrid() async {    await tapButton(find.byType(InsertRowButton));  }  Future<void> tapRowMenuButtonInGrid() async {    await tapButton(find.byType(RowMenuButton));  }  /// Should call [tapRowMenuButtonInGrid] first.  Future<void> tapDeleteOnRowMenu() async {    await tapButtonWithName(LocaleKeys.grid_row_delete.tr());  }  Future<void> assertRowCountInGridPage(int num) async {    final text = find.byWidgetPredicate(      (widget) => widget is FlowyText && widget.text == rowCountString(num),    );    expect(text, findsOneWidget);  }  Future<void> createField(FieldType fieldType, String name) async {    await scrollToRight(find.byType(GridPage));    await tapNewPropertyButton();    await renameField(name);    await tapTypeOptionButton();    await selectFieldType(fieldType);    await dismissFieldEditor();  }  Future<void> tapDatabaseSettingButton() async {    await tapButton(find.byType(SettingButton));  }  Future<void> tapDatabaseFilterButton() async {    await tapButton(find.byType(FilterButton));  }  Future<void> tapDatabaseSortButton() async {    await tapButton(find.byType(SortButton));  }  Future<void> 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<void> tapFilterButtonInGrid(String filterName) async {    final findFilter = find.byType(FilterMenuItem);    final button = find.descendant(      of: findFilter,      matching: find.text(filterName),    );    await tapButton(button);  }  Future<void> 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<void> 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<void> tapSortMenuInSettingBar() async {    await tapButton(find.byType(SortMenu));    await pumpAndSettle();  }  /// Must call [tapSortMenuInSettingBar] first.  Future<void> 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<void> 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<void> tapAllSortButton() async {    await tapButton(find.byType(DatabaseDeleteSortButton));  }  Future<void> scrollOptionFilterListByOffset(Offset offset) async {    await drag(find.byType(SelectOptionFilterEditor), offset);    await pumpAndSettle();  }  Future<void> 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<void> tapDisclosureButtonInFinder(Finder finder) async {    final findDisclosure = find.descendant(      of: finder,      matching: find.byType(DisclosureButton),    );    await tapButton(findDisclosure);  }  /// must call [tapDisclosureButtonInFinder] first.  Future<void> tapDeleteFilterButtonInGrid() async {    await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr()));  }  Future<void> tapCheckboxFilterButtonInGrid() async {    await tapButton(find.byType(CheckboxFilterConditionList));  }  Future<void> tapChecklistFilterButtonInGrid() async {    await tapButton(find.byType(ChecklistFilterConditionList));  }  /// The [SelectOptionFilterList] must show up first.  Future<void> 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<void> tapCheckedButtonOnCheckboxFilter() async {    final button = find.descendant(      of: find.byType(HoverButton),      matching: find.text(LocaleKeys.grid_checkboxFilter_isChecked.tr()),    );    await tapButton(button);  }  Future<void> tapUnCheckedButtonOnCheckboxFilter() async {    final button = find.descendant(      of: find.byType(HoverButton),      matching: find.text(LocaleKeys.grid_checkboxFilter_isUnchecked.tr()),    );    await tapButton(button);  }  Future<void> tapCompletedButtonOnChecklistFilter() async {    final button = find.descendant(      of: find.byType(HoverButton),      matching: find.text(LocaleKeys.grid_checklistFilter_isComplete.tr()),    );    await tapButton(button);  }  Future<void> 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<void> tapViewPropertiesButton() async {    final findSettingItem = find.byType(DatabaseSettingItem);    final findLayoutButton = find.byWidgetPredicate(      (widget) =>          widget is FlowyText &&          widget.text == DatabaseSettingAction.showProperties.title(),    );    final button = find.descendant(      of: findSettingItem,      matching: findLayoutButton,    );    await tapButton(button);  }  /// Should call [tapDatabaseSettingButton] first.  Future<void> 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<void> 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<void> tapFirstDayOfWeek() async {    await tapButton(find.byType(FirstDayOfWeek));  }  Future<void> tapFirstDayOfWeekStartFromSunday() async {    final finder = find.byWidgetPredicate(      (widget) => widget is StartFromButton && widget.dayIndex == 0,    );    await tapButton(finder);  }  Future<void> 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<void> scrollToToday() async {    final todayCell = find.byWidgetPredicate(      (widget) => widget is CalendarDayCard && widget.isToday,    );    final scrollable = find        .descendant(          of: find.byType(MonthView<CalendarDayEvent>),          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<void> hoverOnTodayCalendarCell() async {    final todayCell = find.byWidgetPredicate(      (widget) => widget is CalendarDayCard && widget.isToday,    );    await hoverOnWidget(todayCell);  }  Future<void> 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<void> 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<void> 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<void> dragDropRescheduleCalendarEvent(DateTime startDate) async {    final findEventCard = find.byType(EventCard);    await drag(findEventCard.first, const Offset(0, 300));    await pumpAndSettle();  }  Future<void> 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);  }  Future<void> tapTabBarLinkedViewByViewName(String name) async {    final viewButton = findTabBarLinkViewByViewName(name);    await tapButton(viewButton);  }  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<void> 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<void> 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<void> 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<void> 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<void> assertCurrentDatabaseLayoutType(DatabaseLayoutPB layout) async {    expect(finderForDatabaseLayoutType(layout), findsOneWidget);  }  Future<void> tapDatabaseRawDataButton() async {    await tapButtonWithName(LocaleKeys.importPanel_database.tr());  }  Future<void> tapAddSelectOptionButton() async {    await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr());  }  Future<void> tapViewTogglePropertyVisibilityButtonByName(    String fieldName,  ) async {    final field = find.byWidgetPredicate(      (widget) =>          widget is GridPropertyCell && widget.fieldInfo.name == fieldName,    );    final toggleVisibilityButton =        find.descendant(of: field, matching: find.byType(FlowyIconButton));    await tapButton(toggleVisibilityButton);  }}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(GridTimestampCell, 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');  }}
 |