database_test_op.dart 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. import 'dart:io';
  2. import 'package:appflowy/generated/locale_keys.g.dart';
  3. import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
  4. import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
  5. import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
  6. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
  7. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart';
  8. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart';
  9. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart';
  10. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart';
  11. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart';
  12. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart';
  13. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart';
  14. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart';
  15. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
  16. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart';
  17. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
  18. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
  19. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart';
  20. import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
  21. import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
  22. import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
  23. import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
  24. import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart';
  25. import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart';
  26. import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
  27. import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
  28. import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart';
  29. import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart';
  30. import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
  31. import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
  32. import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
  33. import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
  34. import 'package:easy_localization/easy_localization.dart';
  35. import 'package:flowy_infra_ui/style_widget/icon_button.dart';
  36. import 'package:flowy_infra_ui/style_widget/text_field.dart';
  37. import 'package:flowy_infra_ui/style_widget/text_input.dart';
  38. import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
  39. import 'package:flutter/gestures.dart';
  40. import 'package:flutter/material.dart';
  41. import 'package:flutter/services.dart';
  42. import 'package:flutter_test/flutter_test.dart';
  43. import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
  44. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart';
  45. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
  46. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
  47. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart';
  48. import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
  49. import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
  50. import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart';
  51. import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
  52. import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
  53. import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart';
  54. import 'package:flowy_infra_ui/style_widget/text.dart';
  55. import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
  56. import 'package:table_calendar/table_calendar.dart';
  57. import 'base.dart';
  58. import 'common_operations.dart';
  59. import 'expectation.dart';
  60. import 'package:path/path.dart' as p;
  61. import 'mock/mock_file_picker.dart';
  62. extension AppFlowyDatabaseTest on WidgetTester {
  63. Future<void> openV020database() async {
  64. await initializeAppFlowy();
  65. await tapGoButton();
  66. // expect to see a readme page
  67. expectToSeePageName(readme);
  68. await tapAddButton();
  69. await tapImportButton();
  70. final testFileNames = ['v020.afdb'];
  71. final fileLocation = await currentFileLocation();
  72. for (final fileName in testFileNames) {
  73. final str = await rootBundle.loadString(
  74. p.join(
  75. 'assets/test/workspaces/database',
  76. fileName,
  77. ),
  78. );
  79. File(p.join(fileLocation, fileName)).writeAsStringSync(str);
  80. }
  81. // mock get files
  82. await mockPickFilePaths(testFileNames, name: 'import_files');
  83. await tapDatabaseRawDataButton();
  84. await openPage('v020');
  85. }
  86. Future<void> hoverOnFirstRowOfGrid() async {
  87. final findRow = find.byType(GridRow);
  88. expect(findRow, findsWidgets);
  89. final firstRow = findRow.first;
  90. await hoverOnWidget(firstRow);
  91. }
  92. Future<void> editCell({
  93. required int rowIndex,
  94. required FieldType fieldType,
  95. required String input,
  96. }) async {
  97. final cell = cellFinder(rowIndex, fieldType);
  98. expect(cell, findsOneWidget);
  99. await enterText(cell, input);
  100. await pumpAndSettle();
  101. }
  102. Finder cellFinder(int rowIndex, FieldType fieldType) {
  103. final findRow = find.byType(GridRow, skipOffstage: false);
  104. final findCell = finderForFieldType(fieldType);
  105. return find.descendant(
  106. of: findRow.at(rowIndex),
  107. matching: findCell,
  108. skipOffstage: false,
  109. );
  110. }
  111. Future<void> tapCheckboxCellInGrid({
  112. required int rowIndex,
  113. }) async {
  114. final cell = cellFinder(rowIndex, FieldType.Checkbox);
  115. final button = find.descendant(
  116. of: cell,
  117. matching: find.byType(FlowyIconButton),
  118. );
  119. expect(cell, findsOneWidget);
  120. await tapButton(button);
  121. }
  122. Future<void> assertCheckboxCell({
  123. required int rowIndex,
  124. required bool isSelected,
  125. }) async {
  126. final cell = cellFinder(rowIndex, FieldType.Checkbox);
  127. var finder = find.byType(CheckboxCellUncheck);
  128. if (isSelected) {
  129. finder = find.byType(CheckboxCellCheck);
  130. }
  131. expect(
  132. find.descendant(
  133. of: cell,
  134. matching: finder,
  135. ),
  136. findsOneWidget,
  137. );
  138. }
  139. Future<void> tapCellInGrid({
  140. required int rowIndex,
  141. required FieldType fieldType,
  142. }) async {
  143. final cell = cellFinder(rowIndex, fieldType);
  144. expect(cell, findsOneWidget);
  145. await tapButton(cell, warnIfMissed: false);
  146. }
  147. /// The [fieldName] must be uqniue in the grid.
  148. Future<void> assertCellContent({
  149. required int rowIndex,
  150. required FieldType fieldType,
  151. required String content,
  152. }) async {
  153. final findCell = cellFinder(rowIndex, fieldType);
  154. final findContent = find.descendant(
  155. of: findCell,
  156. matching: find.text(content),
  157. skipOffstage: false,
  158. );
  159. final text = find.descendant(
  160. of: find.byType(TextField),
  161. matching: findContent,
  162. skipOffstage: false,
  163. );
  164. expect(text, findsOneWidget);
  165. }
  166. Future<void> assertSingleSelectOption({
  167. required int rowIndex,
  168. required String content,
  169. }) async {
  170. final findCell = cellFinder(rowIndex, FieldType.SingleSelect);
  171. if (content.isNotEmpty) {
  172. final finder = find.descendant(
  173. of: findCell,
  174. matching: find.byWidgetPredicate(
  175. (widget) => widget is SelectOptionTag && widget.name == content,
  176. ),
  177. );
  178. expect(finder, findsOneWidget);
  179. }
  180. }
  181. Future<void> assertMultiSelectOption({
  182. required int rowIndex,
  183. required List<String> contents,
  184. }) async {
  185. final findCell = cellFinder(rowIndex, FieldType.MultiSelect);
  186. for (final content in contents) {
  187. if (content.isNotEmpty) {
  188. final finder = find.descendant(
  189. of: findCell,
  190. matching: find.byWidgetPredicate(
  191. (widget) => widget is SelectOptionTag && widget.name == content,
  192. ),
  193. );
  194. expect(finder, findsOneWidget);
  195. }
  196. }
  197. }
  198. Future<void> assertChecklistCellInGrid({
  199. required int rowIndex,
  200. required double percent,
  201. }) async {
  202. final findCell = cellFinder(rowIndex, FieldType.Checklist);
  203. final finder = find.descendant(
  204. of: findCell,
  205. matching: find.byWidgetPredicate(
  206. (widget) {
  207. if (widget is ChecklistProgressBar) {
  208. return widget.percent == percent;
  209. }
  210. return false;
  211. },
  212. ),
  213. );
  214. expect(finder, findsOneWidget);
  215. }
  216. Future<void> assertDateCellInGrid({
  217. required int rowIndex,
  218. required FieldType fieldType,
  219. required String content,
  220. }) async {
  221. final findRow = find.byType(GridRow, skipOffstage: false);
  222. final findCell = find.descendant(
  223. of: findRow.at(rowIndex),
  224. matching: find.byWidgetPredicate(
  225. (widget) => widget is GridDateCell && widget.fieldType == fieldType,
  226. ),
  227. skipOffstage: false,
  228. );
  229. final dateCellText = find.descendant(
  230. of: findCell,
  231. matching: find.byType(GridDateCellText),
  232. );
  233. final text = find.descendant(
  234. of: dateCellText,
  235. matching: find.byWidgetPredicate(
  236. (widget) {
  237. if (widget is FlowyText) {
  238. return widget.text == content;
  239. }
  240. return false;
  241. },
  242. ),
  243. skipOffstage: false,
  244. );
  245. expect(text, findsOneWidget);
  246. }
  247. Future<void> selectDay({
  248. required int content,
  249. }) async {
  250. final findCalendar = find.byType(TableCalendar);
  251. final findDay = find.text(content.toString());
  252. final finder = find.descendant(
  253. of: findCalendar,
  254. matching: findDay,
  255. );
  256. await tapButton(finder);
  257. }
  258. Future<void> tapSelectOptionCellInGrid({
  259. required int rowIndex,
  260. required FieldType fieldType,
  261. }) async {
  262. assert(
  263. fieldType == FieldType.SingleSelect || fieldType == FieldType.MultiSelect,
  264. );
  265. final findRow = find.byType(GridRow);
  266. final findCell = finderForFieldType(fieldType);
  267. final cell = find.descendant(
  268. of: findRow.at(rowIndex),
  269. matching: findCell,
  270. );
  271. await tapButton(cell);
  272. }
  273. /// The [SelectOptionCellEditor] must be opened first.
  274. Future<void> createOption({
  275. required String name,
  276. }) async {
  277. final findEditor = find.byType(SelectOptionCellEditor);
  278. expect(findEditor, findsOneWidget);
  279. final findTextField = find.byType(SelectOptionTextField);
  280. expect(findTextField, findsOneWidget);
  281. await enterText(findTextField, name);
  282. await pump();
  283. await testTextInput.receiveAction(TextInputAction.done);
  284. await pumpAndSettle();
  285. }
  286. Future<void> findSelectOptionWithNameInGrid({
  287. required int rowIndex,
  288. required String name,
  289. }) async {
  290. final findRow = find.byType(GridRow);
  291. final option = find.byWidgetPredicate(
  292. (widget) => widget is SelectOptionTag && widget.name == name,
  293. );
  294. final cell = find.descendant(
  295. of: findRow.at(rowIndex),
  296. matching: option,
  297. );
  298. expect(cell, findsOneWidget);
  299. }
  300. Future<void> openFirstRowDetailPage() async {
  301. await hoverOnFirstRowOfGrid();
  302. final expandButton = find.byType(PrimaryCellAccessory);
  303. expect(expandButton, findsOneWidget);
  304. await tapButton(expandButton);
  305. }
  306. Future<void> hoverRowBanner() async {
  307. final banner = find.byType(RowBanner);
  308. expect(banner, findsOneWidget);
  309. await startGesture(
  310. getTopLeft(banner),
  311. kind: PointerDeviceKind.mouse,
  312. );
  313. await pumpAndSettle();
  314. }
  315. Future<void> openEmojiPicker() async {
  316. await tapButton(find.byType(EmojiPickerButton));
  317. await tapButton(find.byType(EmojiSelectionMenu));
  318. }
  319. /// Must call [openEmojiPicker] first
  320. Future<void> switchToEmojiList() async {
  321. final icon = find.byIcon(Icons.tag_faces);
  322. await tapButton(icon);
  323. }
  324. Future<void> tapEmoji(String emoji) async {
  325. final emojiWidget = find.text(emoji);
  326. await tapButton(emojiWidget);
  327. }
  328. Future<void> scrollGridByOffset(Offset offset) async {
  329. await drag(find.byType(GridPage), offset);
  330. await pumpAndSettle();
  331. }
  332. Future<void> scrollRowDetailByOffset(Offset offset) async {
  333. await drag(find.byType(RowDetailPage), offset);
  334. await pumpAndSettle();
  335. }
  336. Future<void> scrollToRight(Finder find) async {
  337. final size = getSize(find);
  338. await drag(find, Offset(-size.width, 0));
  339. await pumpAndSettle(const Duration(milliseconds: 500));
  340. }
  341. Future<void> tapNewPropertyButton() async {
  342. await tapButtonWithName(LocaleKeys.grid_field_newProperty.tr());
  343. await pumpAndSettle();
  344. }
  345. Future<void> tapGridFieldWithName(String name) async {
  346. final field = find.byWidgetPredicate(
  347. (widget) => widget is FieldCellButton && widget.field.name == name,
  348. );
  349. await tapButton(field);
  350. await pumpAndSettle();
  351. }
  352. /// Should call [tapGridFieldWithName] first.
  353. Future<void> tapEditPropertyButton() async {
  354. await tapButtonWithName(LocaleKeys.grid_field_editProperty.tr());
  355. await pumpAndSettle(const Duration(milliseconds: 200));
  356. }
  357. /// Should call [tapGridFieldWithName] first.
  358. Future<void> tapDeletePropertyButton() async {
  359. final field = find.byWidgetPredicate(
  360. (widget) =>
  361. widget is FieldActionCell && widget.action == FieldAction.delete,
  362. );
  363. await tapButton(field);
  364. }
  365. /// Should call [tapGridFieldWithName] first.
  366. Future<void> tapDialogOkButton() async {
  367. final field = find.byWidgetPredicate(
  368. (widget) =>
  369. widget is PrimaryTextButton &&
  370. widget.label == LocaleKeys.button_OK.tr(),
  371. );
  372. await tapButton(field);
  373. }
  374. /// Should call [tapGridFieldWithName] first.
  375. Future<void> tapDuplicatePropertyButton() async {
  376. final field = find.byWidgetPredicate(
  377. (widget) =>
  378. widget is FieldActionCell && widget.action == FieldAction.duplicate,
  379. );
  380. await tapButton(field);
  381. }
  382. /// Should call [tapGridFieldWithName] first.
  383. Future<void> tapHidePropertyButton() async {
  384. final field = find.byWidgetPredicate(
  385. (widget) =>
  386. widget is FieldActionCell && widget.action == FieldAction.hide,
  387. );
  388. await tapButton(field);
  389. }
  390. Future<void> tapRowDetailPageCreatePropertyButton() async {
  391. await tapButton(find.byType(CreateRowFieldButton));
  392. }
  393. Future<void> tapRowDetailPageDeleteRowButton() async {
  394. await tapButton(find.byType(RowDetailPageDeleteButton));
  395. }
  396. Future<void> tapRowDetailPageDuplicateRowButton() async {
  397. await tapButton(find.byType(RowDetailPageDuplicateButton));
  398. }
  399. Future<void> tapTypeOptionButton() async {
  400. await tapButton(find.byType(SwitchFieldButton));
  401. }
  402. Future<void> tapEscButton() async {
  403. await sendKeyEvent(LogicalKeyboardKey.escape);
  404. }
  405. /// Must call [tapTypeOptionButton] first.
  406. Future<void> selectFieldType(FieldType fieldType) async {
  407. final fieldTypeCell = find.byType(FieldTypeCell);
  408. final fieldTypeButton = find.descendant(
  409. of: fieldTypeCell,
  410. matching: find.byWidgetPredicate(
  411. (widget) => widget is FlowyText && widget.text == fieldType.title(),
  412. ),
  413. );
  414. await tapButton(fieldTypeButton);
  415. }
  416. /// Each field has its own cell, so we can find the corresponding cell by
  417. /// the field type after create a new field.
  418. Future<void> findCellByFieldType(FieldType fieldType) async {
  419. final finder = finderForFieldType(fieldType);
  420. expect(finder, findsWidgets);
  421. }
  422. Future<void> assertNumberOfFieldsInGridPage(int num) async {
  423. expect(find.byType(GridFieldCell), findsNWidgets(num));
  424. }
  425. Future<void> assertNumberOfRowsInGridPage(int num) async {
  426. expect(
  427. find.byType(GridRow, skipOffstage: false),
  428. findsNWidgets(num),
  429. );
  430. }
  431. Future<void> assertDocumentExistInRowDetailPage() async {
  432. expect(find.byType(RowDocument), findsOneWidget);
  433. }
  434. /// Check the field type of the [FieldCellButton] is the same as the name.
  435. Future<void> assertFieldTypeWithFieldName(
  436. String name,
  437. FieldType fieldType,
  438. ) async {
  439. final field = find.byWidgetPredicate(
  440. (widget) =>
  441. widget is FieldCellButton &&
  442. widget.field.fieldType == fieldType &&
  443. widget.field.name == name,
  444. );
  445. expect(field, findsOneWidget);
  446. }
  447. Future<void> findFieldWithName(String name) async {
  448. final field = find.byWidgetPredicate(
  449. (widget) => widget is FieldCellButton && widget.field.name == name,
  450. );
  451. expect(field, findsOneWidget);
  452. }
  453. Future<void> noFieldWithName(String name) async {
  454. final field = find.byWidgetPredicate(
  455. (widget) => widget is FieldCellButton && widget.field.name == name,
  456. );
  457. expect(field, findsNothing);
  458. }
  459. Future<void> renameField(String newName) async {
  460. final textField = find.byType(FieldNameTextField);
  461. expect(textField, findsOneWidget);
  462. await enterText(textField, newName);
  463. await pumpAndSettle();
  464. }
  465. Future<void> dismissFieldEditor() async {
  466. await sendKeyEvent(LogicalKeyboardKey.escape);
  467. await pumpAndSettle(const Duration(milliseconds: 200));
  468. }
  469. Future<void> findFieldEditor(dynamic matcher) async {
  470. final finder = find.byType(FieldEditor);
  471. expect(finder, matcher);
  472. }
  473. Future<void> findDateEditor(dynamic matcher) async {
  474. final finder = find.byType(DateCellEditor);
  475. expect(finder, matcher);
  476. }
  477. Future<void> findSelectOptionEditor(dynamic matcher) async {
  478. final finder = find.byType(SelectOptionCellEditor);
  479. expect(finder, matcher);
  480. }
  481. Future<void> dismissSelectOptionEditor() async {
  482. await sendKeyEvent(LogicalKeyboardKey.escape);
  483. await pumpAndSettle();
  484. }
  485. Future<void> tapCreateRowButtonInGrid() async {
  486. await tapButton(find.byType(GridAddRowButton));
  487. }
  488. Future<void> tapCreateRowButtonInRowMenuOfGrid() async {
  489. await tapButton(find.byType(InsertRowButton));
  490. }
  491. Future<void> tapRowMenuButtonInGrid() async {
  492. await tapButton(find.byType(RowMenuButton));
  493. }
  494. /// Should call [tapRowMenuButtonInGrid] first.
  495. Future<void> tapDeleteOnRowMenu() async {
  496. await tapButtonWithName(LocaleKeys.grid_row_delete.tr());
  497. }
  498. Future<void> assertRowCountInGridPage(int num) async {
  499. final text = find.byWidgetPredicate(
  500. (widget) => widget is FlowyText && widget.text == rowCountString(num),
  501. );
  502. expect(text, findsOneWidget);
  503. }
  504. Future<void> createField(FieldType fieldType, String name) async {
  505. await scrollToRight(find.byType(GridPage));
  506. await tapNewPropertyButton();
  507. await renameField(name);
  508. await tapTypeOptionButton();
  509. await selectFieldType(fieldType);
  510. await dismissFieldEditor();
  511. }
  512. Future<void> tapDatabaseSettingButton() async {
  513. await tapButton(find.byType(SettingButton));
  514. }
  515. Future<void> tapDatabaseFilterButton() async {
  516. await tapButton(find.byType(FilterButton));
  517. }
  518. Future<void> tapCreateFilterByFieldType(
  519. FieldType fieldType,
  520. String title,
  521. ) async {
  522. final findFilter = find.byWidgetPredicate(
  523. (widget) =>
  524. widget is GridFilterPropertyCell &&
  525. widget.fieldInfo.fieldType == fieldType &&
  526. widget.fieldInfo.name == title,
  527. );
  528. await tapButton(findFilter);
  529. }
  530. Future<void> tapFilterButtonInGrid(String filterName) async {
  531. final findFilter = find.byType(FilterMenuItem);
  532. final button = find.descendant(
  533. of: findFilter,
  534. matching: find.text(filterName),
  535. );
  536. await tapButton(button);
  537. }
  538. Future<void> scrollOptionFilterListByOffset(Offset offset) async {
  539. await drag(find.byType(SelectOptionFilterEditor), offset);
  540. await pumpAndSettle();
  541. }
  542. Future<void> enterTextInTextFilter(String text) async {
  543. final findEditor = find.byType(TextFilterEditor);
  544. final findTextField = find.descendant(
  545. of: findEditor,
  546. matching: find.byType(FlowyTextField),
  547. );
  548. await enterText(findTextField, text);
  549. await pumpAndSettle(const Duration(milliseconds: 300));
  550. }
  551. Future<void> tapDisclosureButtonInFinder(Finder finder) async {
  552. final findDisclosure = find.descendant(
  553. of: finder,
  554. matching: find.byType(DisclosureButton),
  555. );
  556. await tapButton(findDisclosure);
  557. }
  558. /// must call [tapDisclosureButtonInFinder] first.
  559. Future<void> tapDeleteFilterButtonInGrid() async {
  560. await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr()));
  561. }
  562. Future<void> tapCheckboxFilterButtonInGrid() async {
  563. await tapButton(find.byType(CheckboxFilterConditionList));
  564. }
  565. Future<void> tapChecklistFilterButtonInGrid() async {
  566. await tapButton(find.byType(ChecklistFilterConditionList));
  567. }
  568. /// The [SelectOptionFilterList] must show up first.
  569. Future<void> tapOptionFilterWithName(String name) async {
  570. final findCell = find.descendant(
  571. of: find.byType(SelectOptionFilterList),
  572. matching: find.byWidgetPredicate(
  573. (widget) =>
  574. widget is SelectOptionFilterCell && widget.option.name == name,
  575. skipOffstage: false,
  576. ),
  577. skipOffstage: false,
  578. );
  579. expect(findCell, findsOneWidget);
  580. await tapButton(findCell, warnIfMissed: false);
  581. }
  582. Future<void> tapCheckedButtonOnCheckboxFilter() async {
  583. final button = find.descendant(
  584. of: find.byType(HoverButton),
  585. matching: find.text(LocaleKeys.grid_checkboxFilter_isChecked.tr()),
  586. );
  587. await tapButton(button);
  588. }
  589. Future<void> tapUnCheckedButtonOnCheckboxFilter() async {
  590. final button = find.descendant(
  591. of: find.byType(HoverButton),
  592. matching: find.text(LocaleKeys.grid_checkboxFilter_isUnchecked.tr()),
  593. );
  594. await tapButton(button);
  595. }
  596. Future<void> tapCompletedButtonOnChecklistFilter() async {
  597. final button = find.descendant(
  598. of: find.byType(HoverButton),
  599. matching: find.text(LocaleKeys.grid_checklistFilter_isComplete.tr()),
  600. );
  601. await tapButton(button);
  602. }
  603. Future<void> tapUnCompletedButtonOnChecklistFilter() async {
  604. final button = find.descendant(
  605. of: find.byType(HoverButton),
  606. matching: find.text(LocaleKeys.grid_checklistFilter_isIncomplted.tr()),
  607. );
  608. await tapButton(button);
  609. }
  610. /// Should call [tapDatabaseSettingButton] first.
  611. Future<void> tapDatabaseLayoutButton() async {
  612. final findSettingItem = find.byType(DatabaseSettingItem);
  613. final findLayoutButton = find.byWidgetPredicate(
  614. (widget) =>
  615. widget is FlowyText &&
  616. widget.text == DatabaseSettingAction.showLayout.title(),
  617. );
  618. final button = find.descendant(
  619. of: findSettingItem,
  620. matching: findLayoutButton,
  621. );
  622. await tapButton(button);
  623. }
  624. Future<void> tapCalendarLayoutSettingButton() async {
  625. final findSettingItem = find.byType(DatabaseSettingItem);
  626. final findLayoutButton = find.byWidgetPredicate(
  627. (widget) =>
  628. widget is FlowyText &&
  629. widget.text == DatabaseSettingAction.showCalendarLayout.title(),
  630. );
  631. final button = find.descendant(
  632. of: findSettingItem,
  633. matching: findLayoutButton,
  634. );
  635. await tapButton(button);
  636. }
  637. Future<void> tapFirstDayOfWeek() async {
  638. await tapButton(find.byType(FirstDayOfWeek));
  639. }
  640. Future<void> tapFirstDayOfWeekStartFromSunday() async {
  641. final finder = find.byWidgetPredicate(
  642. (widget) => widget is StartFromButton && widget.dayIndex == 0,
  643. );
  644. await tapButton(finder);
  645. }
  646. Future<void> tapFirstDayOfWeekStartFromMonday() async {
  647. final finder = find.byWidgetPredicate(
  648. (widget) => widget is StartFromButton && widget.dayIndex == 1,
  649. );
  650. await tapButton(finder);
  651. // Dismiss the popover overlay in cause of obscure the tapButton
  652. // in the next test case.
  653. await sendKeyEvent(LogicalKeyboardKey.escape);
  654. await pumpAndSettle(const Duration(milliseconds: 200));
  655. }
  656. void assertFirstDayOfWeekStartFromMonday() {
  657. final finder = find.byWidgetPredicate(
  658. (widget) =>
  659. widget is StartFromButton &&
  660. widget.dayIndex == 1 &&
  661. widget.isSelected == true,
  662. );
  663. expect(finder, findsOneWidget);
  664. }
  665. void assertFirstDayOfWeekStartFromSunday() {
  666. final finder = find.byWidgetPredicate(
  667. (widget) =>
  668. widget is StartFromButton &&
  669. widget.dayIndex == 0 &&
  670. widget.isSelected == true,
  671. );
  672. expect(finder, findsOneWidget);
  673. }
  674. Future<void> tapCreateLinkedDatabaseViewButton(AddButtonAction action) async {
  675. final findAddButton = find.byType(AddDatabaseViewButton);
  676. await tapButton(findAddButton);
  677. final findCreateButton = find.byWidgetPredicate(
  678. (widget) =>
  679. widget is TarBarAddButtonActionCell && widget.action == action,
  680. );
  681. await tapButton(findCreateButton);
  682. }
  683. Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) {
  684. return find.byWidgetPredicate(
  685. (widget) => widget is TabBarItemButton && widget.view.layout == layout,
  686. );
  687. }
  688. Finder findTabBarLinkViewByViewName(String name) {
  689. return find.byWidgetPredicate(
  690. (widget) => widget is TabBarItemButton && widget.view.name == name,
  691. );
  692. }
  693. Future<void> renameLinkedView(Finder linkedView, String name) async {
  694. await tap(linkedView, buttons: kSecondaryButton);
  695. await pumpAndSettle();
  696. await tapButton(
  697. find.byWidgetPredicate(
  698. (widget) =>
  699. widget is ActionCellWidget &&
  700. widget.action == TabBarViewAction.rename,
  701. ),
  702. );
  703. await enterText(
  704. find.descendant(
  705. of: find.byType(FlowyFormTextInput),
  706. matching: find.byType(TextFormField),
  707. ),
  708. name,
  709. );
  710. final field = find.byWidgetPredicate(
  711. (widget) =>
  712. widget is PrimaryTextButton &&
  713. widget.label == LocaleKeys.button_OK.tr(),
  714. );
  715. await tapButton(field);
  716. }
  717. Future<void> deleteDatebaseView(Finder linkedView) async {
  718. await tap(linkedView, buttons: kSecondaryButton);
  719. await pumpAndSettle();
  720. await tapButton(
  721. find.byWidgetPredicate(
  722. (widget) =>
  723. widget is ActionCellWidget &&
  724. widget.action == TabBarViewAction.delete,
  725. ),
  726. );
  727. final okButton = find.byWidgetPredicate(
  728. (widget) =>
  729. widget is PrimaryTextButton &&
  730. widget.label == LocaleKeys.button_OK.tr(),
  731. );
  732. await tapButton(okButton);
  733. }
  734. Future<void> assertCurrentDatabaseTagIs(DatabaseLayoutPB layout) async {
  735. switch (layout) {
  736. case DatabaseLayoutPB.Board:
  737. expect(find.byType(BoardPage), findsOneWidget);
  738. break;
  739. case DatabaseLayoutPB.Calendar:
  740. expect(find.byType(CalendarPage), findsOneWidget);
  741. break;
  742. case DatabaseLayoutPB.Grid:
  743. expect(find.byType(GridPage), findsOneWidget);
  744. break;
  745. default:
  746. throw Exception('Unknown database layout type: $layout');
  747. }
  748. }
  749. Future<void> selectDatabaseLayoutType(DatabaseLayoutPB layout) async {
  750. final findLayoutCell = find.byType(DatabaseViewLayoutCell);
  751. final findText = find.byWidgetPredicate(
  752. (widget) => widget is FlowyText && widget.text == layout.layoutName(),
  753. );
  754. final button = find.descendant(
  755. of: findLayoutCell,
  756. matching: findText,
  757. );
  758. await tapButton(button);
  759. }
  760. Future<void> assertCurrentDatabaseLayoutType(DatabaseLayoutPB layout) async {
  761. expect(finderForDatabaseLayoutType(layout), findsOneWidget);
  762. }
  763. Future<void> tapDatabaseRawDataButton() async {
  764. await tapButtonWithName(LocaleKeys.importPanel_database.tr());
  765. }
  766. }
  767. Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
  768. switch (layout) {
  769. case DatabaseLayoutPB.Board:
  770. return find.byType(BoardPage);
  771. case DatabaseLayoutPB.Calendar:
  772. return find.byType(CalendarPage);
  773. case DatabaseLayoutPB.Grid:
  774. return find.byType(GridPage);
  775. default:
  776. throw Exception('Unknown database layout type: $layout');
  777. }
  778. }
  779. Finder finderForFieldType(FieldType fieldType) {
  780. switch (fieldType) {
  781. case FieldType.Checkbox:
  782. return find.byType(GridCheckboxCell, skipOffstage: false);
  783. case FieldType.DateTime:
  784. return find.byType(GridDateCell, skipOffstage: false);
  785. case FieldType.LastEditedTime:
  786. case FieldType.CreatedTime:
  787. return find.byType(GridDateCell, skipOffstage: false);
  788. case FieldType.SingleSelect:
  789. return find.byType(GridSingleSelectCell, skipOffstage: false);
  790. case FieldType.MultiSelect:
  791. return find.byType(GridMultiSelectCell, skipOffstage: false);
  792. case FieldType.Checklist:
  793. return find.byType(GridChecklistCell, skipOffstage: false);
  794. case FieldType.Number:
  795. return find.byType(GridNumberCell, skipOffstage: false);
  796. case FieldType.RichText:
  797. return find.byType(GridTextCell, skipOffstage: false);
  798. case FieldType.URL:
  799. return find.byType(GridURLCell, skipOffstage: false);
  800. default:
  801. throw Exception('Unknown field type: $fieldType');
  802. }
  803. }