database_test_op.dart 25 KB

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