database_test_op.dart 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213
  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_day.dart';
  5. import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
  6. import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.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/sort/create_sort_list.dart';
  20. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/order_panel.dart';
  21. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart';
  22. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_menu.dart';
  23. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
  24. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
  25. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart';
  26. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart';
  27. import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
  28. import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
  29. import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
  30. import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
  31. import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart';
  32. import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart';
  33. import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
  34. import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
  35. import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart';
  36. import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart';
  37. import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
  38. import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
  39. import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
  40. import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
  41. import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
  42. import 'package:easy_localization/easy_localization.dart';
  43. import 'package:flowy_infra_ui/flowy_infra_ui.dart';
  44. import 'package:flowy_infra_ui/style_widget/text_input.dart';
  45. import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
  46. import 'package:flutter/gestures.dart';
  47. import 'package:flutter/material.dart';
  48. import 'package:flutter/services.dart';
  49. import 'package:flutter_test/flutter_test.dart';
  50. import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
  51. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart';
  52. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
  53. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
  54. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart';
  55. import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
  56. import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
  57. import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart';
  58. import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
  59. import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
  60. import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart';
  61. import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
  62. import 'package:table_calendar/table_calendar.dart';
  63. import 'base.dart';
  64. import 'common_operations.dart';
  65. import 'expectation.dart';
  66. import 'package:path/path.dart' as p;
  67. import 'mock/mock_file_picker.dart';
  68. extension AppFlowyDatabaseTest on WidgetTester {
  69. Future<void> openV020database() async {
  70. await initializeAppFlowy();
  71. await tapGoButton();
  72. // expect to see a readme page
  73. expectToSeePageName(readme);
  74. await tapAddButton();
  75. await tapImportButton();
  76. final testFileNames = ['v020.afdb'];
  77. final fileLocation = await currentFileLocation();
  78. for (final fileName in testFileNames) {
  79. final str = await rootBundle.loadString(
  80. p.join(
  81. 'assets/test/workspaces/database',
  82. fileName,
  83. ),
  84. );
  85. File(p.join(fileLocation, fileName)).writeAsStringSync(str);
  86. }
  87. // mock get files
  88. await mockPickFilePaths(testFileNames, name: 'import_files');
  89. await tapDatabaseRawDataButton();
  90. await openPage('v020');
  91. }
  92. Future<void> hoverOnFirstRowOfGrid() async {
  93. final findRow = find.byType(GridRow);
  94. expect(findRow, findsWidgets);
  95. final firstRow = findRow.first;
  96. await hoverOnWidget(firstRow);
  97. }
  98. Future<void> editCell({
  99. required int rowIndex,
  100. required FieldType fieldType,
  101. required String input,
  102. int cellIndex = 0,
  103. }) async {
  104. final cell = cellFinder(rowIndex, fieldType, cellIndex: cellIndex);
  105. expect(cell, findsOneWidget);
  106. await enterText(cell, input);
  107. await pumpAndSettle();
  108. }
  109. ///
  110. Finder cellFinder(int rowIndex, FieldType fieldType, {int cellIndex = 0}) {
  111. final findRow = find.byType(GridRow, skipOffstage: false);
  112. final findCell = finderForFieldType(fieldType);
  113. return find
  114. .descendant(
  115. of: findRow.at(rowIndex),
  116. matching: findCell,
  117. skipOffstage: false,
  118. )
  119. .at(cellIndex);
  120. }
  121. Future<void> tapCheckboxCellInGrid({
  122. required int rowIndex,
  123. }) async {
  124. final cell = cellFinder(rowIndex, FieldType.Checkbox);
  125. final button = find.descendant(
  126. of: cell,
  127. matching: find.byType(FlowyIconButton),
  128. );
  129. expect(cell, findsOneWidget);
  130. await tapButton(button);
  131. }
  132. Future<void> assertCheckboxCell({
  133. required int rowIndex,
  134. required bool isSelected,
  135. }) async {
  136. final cell = cellFinder(rowIndex, FieldType.Checkbox);
  137. var finder = find.byType(CheckboxCellUncheck);
  138. if (isSelected) {
  139. finder = find.byType(CheckboxCellCheck);
  140. }
  141. expect(
  142. find.descendant(
  143. of: cell,
  144. matching: finder,
  145. ),
  146. findsOneWidget,
  147. );
  148. }
  149. Future<void> tapCellInGrid({
  150. required int rowIndex,
  151. required FieldType fieldType,
  152. }) async {
  153. final cell = cellFinder(rowIndex, fieldType);
  154. expect(cell, findsOneWidget);
  155. await tapButton(cell, warnIfMissed: false);
  156. }
  157. /// The [fieldName] must be unique in the grid.
  158. Future<void> assertCellContent({
  159. required int rowIndex,
  160. required FieldType fieldType,
  161. required String content,
  162. int cellIndex = 0,
  163. }) async {
  164. final findCell = cellFinder(rowIndex, fieldType, cellIndex: cellIndex);
  165. final findContent = find.descendant(
  166. of: findCell,
  167. matching: find.text(content),
  168. skipOffstage: false,
  169. );
  170. final text = find.descendant(
  171. of: find.byType(TextField),
  172. matching: findContent,
  173. skipOffstage: false,
  174. );
  175. expect(text, findsOneWidget);
  176. }
  177. Future<void> assertSingleSelectOption({
  178. required int rowIndex,
  179. required String content,
  180. }) async {
  181. final findCell = cellFinder(rowIndex, FieldType.SingleSelect);
  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. Future<void> assertMultiSelectOption({
  193. required int rowIndex,
  194. required List<String> contents,
  195. }) async {
  196. final findCell = cellFinder(rowIndex, FieldType.MultiSelect);
  197. for (final content in contents) {
  198. if (content.isNotEmpty) {
  199. final finder = find.descendant(
  200. of: findCell,
  201. matching: find.byWidgetPredicate(
  202. (widget) => widget is SelectOptionTag && widget.name == content,
  203. ),
  204. );
  205. expect(finder, findsOneWidget);
  206. }
  207. }
  208. }
  209. Future<void> assertChecklistCellInGrid({
  210. required int rowIndex,
  211. required double percent,
  212. }) async {
  213. final findCell = cellFinder(rowIndex, FieldType.Checklist);
  214. final finder = find.descendant(
  215. of: findCell,
  216. matching: find.byWidgetPredicate(
  217. (widget) {
  218. if (widget is ChecklistProgressBar) {
  219. return widget.percent == percent;
  220. }
  221. return false;
  222. },
  223. ),
  224. );
  225. expect(finder, findsOneWidget);
  226. }
  227. Future<void> assertDateCellInGrid({
  228. required int rowIndex,
  229. required FieldType fieldType,
  230. required String content,
  231. }) async {
  232. final findRow = find.byType(GridRow, skipOffstage: false);
  233. final findCell = find.descendant(
  234. of: findRow.at(rowIndex),
  235. matching: find.byWidgetPredicate(
  236. (widget) => widget is GridDateCell && widget.fieldType == fieldType,
  237. ),
  238. skipOffstage: false,
  239. );
  240. final dateCellText = find.descendant(
  241. of: findCell,
  242. matching: find.byType(GridDateCellText),
  243. );
  244. final text = find.descendant(
  245. of: dateCellText,
  246. matching: find.byWidgetPredicate(
  247. (widget) {
  248. if (widget is FlowyText) {
  249. return widget.text == content;
  250. }
  251. return false;
  252. },
  253. ),
  254. skipOffstage: false,
  255. );
  256. expect(text, findsOneWidget);
  257. }
  258. Future<void> selectDay({
  259. required int content,
  260. }) async {
  261. final findCalendar = find.byType(TableCalendar);
  262. final findDay = find.text(content.toString());
  263. final finder = find.descendant(
  264. of: findCalendar,
  265. matching: findDay,
  266. );
  267. await tapButton(finder);
  268. }
  269. Future<void> toggleIncludeTime() async {
  270. final findDateEditor = find.byType(DateCellEditor);
  271. final findToggle = find.byType(Toggle);
  272. final finder = find.descendant(
  273. of: findDateEditor,
  274. matching: findToggle,
  275. );
  276. await tapButton(finder);
  277. }
  278. Future<void> changeDateFormat() async {
  279. final findDateEditor = find.byType(DateCellEditor);
  280. final findDateTimeOptionButton = find.byType(DateTypeOptionButton);
  281. final finder = find.descendant(
  282. of: findDateEditor,
  283. matching: findDateTimeOptionButton,
  284. );
  285. await tapButton(finder);
  286. final findDateFormatButton = find.byType(DateFormatButton);
  287. await tapButton(findDateFormatButton);
  288. final findNewDateFormat = find.text("Day/Month/Year");
  289. await tapButton(findNewDateFormat);
  290. }
  291. Future<void> changeTimeFormat() async {
  292. final findDateEditor = find.byType(DateCellEditor);
  293. final findDateTimeOptionButton = find.byType(DateTypeOptionButton);
  294. final finder = find.descendant(
  295. of: findDateEditor,
  296. matching: findDateTimeOptionButton,
  297. );
  298. await tapButton(finder);
  299. final findDateFormatButton = find.byType(TimeFormatButton);
  300. await tapButton(findDateFormatButton);
  301. final findNewDateFormat = find.text("12 hour");
  302. await tapButton(findNewDateFormat);
  303. }
  304. Future<void> tapSelectOptionCellInGrid({
  305. required int rowIndex,
  306. required FieldType fieldType,
  307. }) async {
  308. assert(
  309. fieldType == FieldType.SingleSelect || fieldType == FieldType.MultiSelect,
  310. );
  311. final findRow = find.byType(GridRow);
  312. final findCell = finderForFieldType(fieldType);
  313. final cell = find.descendant(
  314. of: findRow.at(rowIndex),
  315. matching: findCell,
  316. );
  317. await tapButton(cell);
  318. }
  319. /// The [SelectOptionCellEditor] must be opened first.
  320. Future<void> createOption({
  321. required String name,
  322. }) async {
  323. final findEditor = find.byType(SelectOptionCellEditor);
  324. expect(findEditor, findsOneWidget);
  325. final findTextField = find.byType(SelectOptionTextField);
  326. expect(findTextField, findsOneWidget);
  327. await enterText(findTextField, name);
  328. await pump();
  329. await testTextInput.receiveAction(TextInputAction.done);
  330. await pumpAndSettle();
  331. }
  332. Future<void> selectOption({
  333. required String name,
  334. }) async {
  335. final option = find.byWidgetPredicate(
  336. (widget) => widget is SelectOptionTagCell && widget.option.name == name,
  337. );
  338. await tapButton(option);
  339. }
  340. Future<void> findSelectOptionWithNameInGrid({
  341. required int rowIndex,
  342. required String name,
  343. }) async {
  344. final findRow = find.byType(GridRow);
  345. final option = find.byWidgetPredicate(
  346. (widget) => widget is SelectOptionTag && widget.name == name,
  347. );
  348. final cell = find.descendant(
  349. of: findRow.at(rowIndex),
  350. matching: option,
  351. );
  352. expect(cell, findsOneWidget);
  353. }
  354. Future<void> assertNumberOfSelectedOptionsInGrid({
  355. required int rowIndex,
  356. required Matcher matcher,
  357. }) async {
  358. final findRow = find.byType(GridRow);
  359. final options = find.byWidgetPredicate(
  360. (widget) => widget is SelectOptionTag,
  361. );
  362. final cell = find.descendant(
  363. of: findRow.at(rowIndex),
  364. matching: options,
  365. );
  366. expect(cell, matcher);
  367. }
  368. Future<void> openFirstRowDetailPage() async {
  369. await hoverOnFirstRowOfGrid();
  370. final expandButton = find.byType(PrimaryCellAccessory);
  371. expect(expandButton, findsOneWidget);
  372. await tapButton(expandButton);
  373. }
  374. void assertRowDetailPageOpened() async {
  375. final findRowDetailPage = find.byType(RowDetailPage);
  376. expect(findRowDetailPage, findsOneWidget);
  377. }
  378. Future<void> dismissRowDetailPage() async {
  379. await sendKeyEvent(LogicalKeyboardKey.escape);
  380. await pumpAndSettle();
  381. }
  382. Future<void> editTitleInRowDetailPage(String title) async {
  383. final titleField = find.byType(GridTextCell);
  384. await enterText(titleField, title);
  385. await pumpAndSettle();
  386. }
  387. Future<void> hoverRowBanner() async {
  388. final banner = find.byType(RowBanner);
  389. expect(banner, findsOneWidget);
  390. await startGesture(
  391. getTopLeft(banner),
  392. kind: PointerDeviceKind.mouse,
  393. );
  394. await pumpAndSettle();
  395. }
  396. Future<void> openEmojiPicker() async {
  397. await tapButton(find.byType(EmojiPickerButton));
  398. await tapButton(find.byType(EmojiSelectionMenu));
  399. }
  400. /// Must call [openEmojiPicker] first
  401. Future<void> switchToEmojiList() async {
  402. final icon = find.byIcon(Icons.tag_faces);
  403. await tapButton(icon);
  404. }
  405. Future<void> tapEmoji(String emoji) async {
  406. final emojiWidget = find.text(emoji);
  407. await tapButton(emojiWidget);
  408. }
  409. Future<void> tapDateCellInRowDetailPage() async {
  410. final findDateCell = find.byType(GridDateCell);
  411. await tapButton(findDateCell);
  412. }
  413. Future<void> duplicateRowInRowDetailPage() async {
  414. final duplicateButton = find.byType(RowDetailPageDuplicateButton);
  415. await tapButton(duplicateButton);
  416. }
  417. Future<void> deleteRowInRowDetailPage() async {
  418. final deleteButton = find.byType(RowDetailPageDeleteButton);
  419. await tapButton(deleteButton);
  420. }
  421. Future<void> scrollGridByOffset(Offset offset) async {
  422. await drag(find.byType(GridPage), offset);
  423. await pumpAndSettle();
  424. }
  425. Future<void> scrollRowDetailByOffset(Offset offset) async {
  426. await drag(find.byType(RowDetailPage), offset);
  427. await pumpAndSettle();
  428. }
  429. Future<void> scrollToRight(Finder find) async {
  430. final size = getSize(find);
  431. await drag(find, Offset(-size.width, 0));
  432. await pumpAndSettle(const Duration(milliseconds: 500));
  433. }
  434. Future<void> tapNewPropertyButton() async {
  435. await tapButtonWithName(LocaleKeys.grid_field_newProperty.tr());
  436. await pumpAndSettle();
  437. }
  438. Future<void> tapGridFieldWithName(String name) async {
  439. final field = find.byWidgetPredicate(
  440. (widget) => widget is FieldCellButton && widget.field.name == name,
  441. );
  442. await tapButton(field);
  443. await pumpAndSettle();
  444. }
  445. /// Should call [tapGridFieldWithName] first.
  446. Future<void> tapEditPropertyButton() async {
  447. await tapButtonWithName(LocaleKeys.grid_field_editProperty.tr());
  448. await pumpAndSettle(const Duration(milliseconds: 200));
  449. }
  450. /// Should call [tapGridFieldWithName] first.
  451. Future<void> tapDeletePropertyButton() async {
  452. final field = find.byWidgetPredicate(
  453. (widget) =>
  454. widget is FieldActionCell && widget.action == FieldAction.delete,
  455. );
  456. await tapButton(field);
  457. }
  458. /// Should call [tapGridFieldWithName] first.
  459. Future<void> tapDialogOkButton() async {
  460. final field = find.byWidgetPredicate(
  461. (widget) =>
  462. widget is PrimaryTextButton &&
  463. widget.label == LocaleKeys.button_OK.tr(),
  464. );
  465. await tapButton(field);
  466. }
  467. /// Should call [tapGridFieldWithName] first.
  468. Future<void> tapDuplicatePropertyButton() async {
  469. final field = find.byWidgetPredicate(
  470. (widget) =>
  471. widget is FieldActionCell && widget.action == FieldAction.duplicate,
  472. );
  473. await tapButton(field);
  474. }
  475. /// Should call [tapGridFieldWithName] first.
  476. Future<void> tapHidePropertyButton() async {
  477. final field = find.byWidgetPredicate(
  478. (widget) =>
  479. widget is FieldActionCell && widget.action == FieldAction.hide,
  480. );
  481. await tapButton(field);
  482. }
  483. Future<void> tapRowDetailPageCreatePropertyButton() async {
  484. await tapButton(find.byType(CreateRowFieldButton));
  485. }
  486. Future<void> tapRowDetailPageDeleteRowButton() async {
  487. await tapButton(find.byType(RowDetailPageDeleteButton));
  488. }
  489. Future<void> tapRowDetailPageDuplicateRowButton() async {
  490. await tapButton(find.byType(RowDetailPageDuplicateButton));
  491. }
  492. Future<void> tapTypeOptionButton() async {
  493. await tapButton(find.byType(SwitchFieldButton));
  494. }
  495. Future<void> tapEscButton() async {
  496. await sendKeyEvent(LogicalKeyboardKey.escape);
  497. }
  498. /// Must call [tapTypeOptionButton] first.
  499. Future<void> selectFieldType(FieldType fieldType) async {
  500. final fieldTypeCell = find.byType(FieldTypeCell);
  501. final fieldTypeButton = find.descendant(
  502. of: fieldTypeCell,
  503. matching: find.byWidgetPredicate(
  504. (widget) => widget is FlowyText && widget.text == fieldType.title(),
  505. ),
  506. );
  507. await tapButton(fieldTypeButton);
  508. }
  509. /// Each field has its own cell, so we can find the corresponding cell by
  510. /// the field type after create a new field.
  511. Future<void> findCellByFieldType(FieldType fieldType) async {
  512. final finder = finderForFieldType(fieldType);
  513. expect(finder, findsWidgets);
  514. }
  515. Future<void> assertNumberOfFieldsInGridPage(int num) async {
  516. expect(find.byType(GridFieldCell), findsNWidgets(num));
  517. }
  518. Future<void> assertNumberOfRowsInGridPage(int num) async {
  519. expect(
  520. find.byType(GridRow, skipOffstage: false),
  521. findsNWidgets(num),
  522. );
  523. }
  524. Future<void> assertDocumentExistInRowDetailPage() async {
  525. expect(find.byType(RowDocument), findsOneWidget);
  526. }
  527. /// Check the field type of the [FieldCellButton] is the same as the name.
  528. Future<void> assertFieldTypeWithFieldName(
  529. String name,
  530. FieldType fieldType,
  531. ) async {
  532. final field = find.byWidgetPredicate(
  533. (widget) =>
  534. widget is FieldCellButton &&
  535. widget.field.fieldType == fieldType &&
  536. widget.field.name == name,
  537. );
  538. expect(field, findsOneWidget);
  539. }
  540. Future<void> findFieldWithName(String name) async {
  541. final field = find.byWidgetPredicate(
  542. (widget) => widget is FieldCellButton && widget.field.name == name,
  543. );
  544. expect(field, findsOneWidget);
  545. }
  546. Future<void> noFieldWithName(String name) async {
  547. final field = find.byWidgetPredicate(
  548. (widget) => widget is FieldCellButton && widget.field.name == name,
  549. );
  550. expect(field, findsNothing);
  551. }
  552. Future<void> renameField(String newName) async {
  553. final textField = find.byType(FieldNameTextField);
  554. expect(textField, findsOneWidget);
  555. await enterText(textField, newName);
  556. await pumpAndSettle();
  557. }
  558. Future<void> dismissFieldEditor() async {
  559. await sendKeyEvent(LogicalKeyboardKey.escape);
  560. await pumpAndSettle(const Duration(milliseconds: 200));
  561. }
  562. Future<void> findFieldEditor(dynamic matcher) async {
  563. final finder = find.byType(FieldEditor);
  564. expect(finder, matcher);
  565. }
  566. Future<void> findDateEditor(dynamic matcher) async {
  567. final finder = find.byType(DateCellEditor);
  568. expect(finder, matcher);
  569. }
  570. Future<void> findSelectOptionEditor(dynamic matcher) async {
  571. final finder = find.byType(SelectOptionCellEditor);
  572. expect(finder, matcher);
  573. }
  574. Future<void> dismissCellEditor() async {
  575. await sendKeyEvent(LogicalKeyboardKey.escape);
  576. await pumpAndSettle();
  577. }
  578. Future<void> tapCreateRowButtonInGrid() async {
  579. await tapButton(find.byType(GridAddRowButton));
  580. }
  581. Future<void> tapCreateRowButtonInRowMenuOfGrid() async {
  582. await tapButton(find.byType(InsertRowButton));
  583. }
  584. Future<void> tapRowMenuButtonInGrid() async {
  585. await tapButton(find.byType(RowMenuButton));
  586. }
  587. /// Should call [tapRowMenuButtonInGrid] first.
  588. Future<void> tapDeleteOnRowMenu() async {
  589. await tapButtonWithName(LocaleKeys.grid_row_delete.tr());
  590. }
  591. Future<void> assertRowCountInGridPage(int num) async {
  592. final text = find.byWidgetPredicate(
  593. (widget) => widget is FlowyText && widget.text == rowCountString(num),
  594. );
  595. expect(text, findsOneWidget);
  596. }
  597. Future<void> createField(FieldType fieldType, String name) async {
  598. await scrollToRight(find.byType(GridPage));
  599. await tapNewPropertyButton();
  600. await renameField(name);
  601. await tapTypeOptionButton();
  602. await selectFieldType(fieldType);
  603. await dismissFieldEditor();
  604. }
  605. Future<void> tapDatabaseSettingButton() async {
  606. await tapButton(find.byType(SettingButton));
  607. }
  608. Future<void> tapDatabaseFilterButton() async {
  609. await tapButton(find.byType(FilterButton));
  610. }
  611. Future<void> tapDatabaseSortButton() async {
  612. await tapButton(find.byType(SortButton));
  613. }
  614. Future<void> tapCreateFilterByFieldType(
  615. FieldType fieldType,
  616. String title,
  617. ) async {
  618. final findFilter = find.byWidgetPredicate(
  619. (widget) =>
  620. widget is GridFilterPropertyCell &&
  621. widget.fieldInfo.fieldType == fieldType &&
  622. widget.fieldInfo.name == title,
  623. );
  624. await tapButton(findFilter);
  625. }
  626. Future<void> tapFilterButtonInGrid(String filterName) async {
  627. final findFilter = find.byType(FilterMenuItem);
  628. final button = find.descendant(
  629. of: findFilter,
  630. matching: find.text(filterName),
  631. );
  632. await tapButton(button);
  633. }
  634. Future<void> tapCreateSortByFieldType(
  635. FieldType fieldType,
  636. String title,
  637. ) async {
  638. final findSort = find.byWidgetPredicate(
  639. (widget) =>
  640. widget is GridSortPropertyCell &&
  641. widget.fieldInfo.fieldType == fieldType &&
  642. widget.fieldInfo.name == title,
  643. );
  644. await tapButton(findSort);
  645. }
  646. // Must call [tapSortMenuInSettingBar] first.
  647. Future<void> tapCreateSortByFieldTypeInSortMenu(
  648. FieldType fieldType,
  649. String title,
  650. ) async {
  651. await tapButton(find.byType(DatabaseAddSortButton));
  652. final findSort = find.byWidgetPredicate(
  653. (widget) =>
  654. widget is GridSortPropertyCell &&
  655. widget.fieldInfo.fieldType == fieldType &&
  656. widget.fieldInfo.name == title,
  657. );
  658. await tapButton(findSort);
  659. await pumpAndSettle();
  660. }
  661. Future<void> tapSortMenuInSettingBar() async {
  662. await tapButton(find.byType(SortMenu));
  663. await pumpAndSettle();
  664. }
  665. /// Must call [tapSortMenuInSettingBar] first.
  666. Future<void> tapSortButtonByName(String name) async {
  667. final findSortItem = find.byWidgetPredicate(
  668. (widget) =>
  669. widget is DatabaseSortItem && widget.sortInfo.fieldInfo.name == name,
  670. );
  671. await tapButton(findSortItem);
  672. }
  673. /// Must call [tapSortButtonByName] first.
  674. Future<void> tapSortByDescending() async {
  675. await tapButton(
  676. find.descendant(
  677. of: find.byType(OrderPannelItem),
  678. matching: find.byWidgetPredicate(
  679. (widget) =>
  680. widget is FlowyText &&
  681. widget.text == LocaleKeys.grid_sort_descending.tr(),
  682. ),
  683. ),
  684. );
  685. await sendKeyEvent(LogicalKeyboardKey.escape);
  686. await pumpAndSettle();
  687. }
  688. /// Must call [tapSortMenuInSettingBar] first.
  689. Future<void> tapAllSortButton() async {
  690. await tapButton(find.byType(DatabaseDeleteSortButton));
  691. }
  692. Future<void> scrollOptionFilterListByOffset(Offset offset) async {
  693. await drag(find.byType(SelectOptionFilterEditor), offset);
  694. await pumpAndSettle();
  695. }
  696. Future<void> enterTextInTextFilter(String text) async {
  697. final findEditor = find.byType(TextFilterEditor);
  698. final findTextField = find.descendant(
  699. of: findEditor,
  700. matching: find.byType(FlowyTextField),
  701. );
  702. await enterText(findTextField, text);
  703. await pumpAndSettle(const Duration(milliseconds: 300));
  704. }
  705. Future<void> tapDisclosureButtonInFinder(Finder finder) async {
  706. final findDisclosure = find.descendant(
  707. of: finder,
  708. matching: find.byType(DisclosureButton),
  709. );
  710. await tapButton(findDisclosure);
  711. }
  712. /// must call [tapDisclosureButtonInFinder] first.
  713. Future<void> tapDeleteFilterButtonInGrid() async {
  714. await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr()));
  715. }
  716. Future<void> tapCheckboxFilterButtonInGrid() async {
  717. await tapButton(find.byType(CheckboxFilterConditionList));
  718. }
  719. Future<void> tapChecklistFilterButtonInGrid() async {
  720. await tapButton(find.byType(ChecklistFilterConditionList));
  721. }
  722. /// The [SelectOptionFilterList] must show up first.
  723. Future<void> tapOptionFilterWithName(String name) async {
  724. final findCell = find.descendant(
  725. of: find.byType(SelectOptionFilterList),
  726. matching: find.byWidgetPredicate(
  727. (widget) =>
  728. widget is SelectOptionFilterCell && widget.option.name == name,
  729. skipOffstage: false,
  730. ),
  731. skipOffstage: false,
  732. );
  733. expect(findCell, findsOneWidget);
  734. await tapButton(findCell, warnIfMissed: false);
  735. }
  736. Future<void> tapCheckedButtonOnCheckboxFilter() async {
  737. final button = find.descendant(
  738. of: find.byType(HoverButton),
  739. matching: find.text(LocaleKeys.grid_checkboxFilter_isChecked.tr()),
  740. );
  741. await tapButton(button);
  742. }
  743. Future<void> tapUnCheckedButtonOnCheckboxFilter() async {
  744. final button = find.descendant(
  745. of: find.byType(HoverButton),
  746. matching: find.text(LocaleKeys.grid_checkboxFilter_isUnchecked.tr()),
  747. );
  748. await tapButton(button);
  749. }
  750. Future<void> tapCompletedButtonOnChecklistFilter() async {
  751. final button = find.descendant(
  752. of: find.byType(HoverButton),
  753. matching: find.text(LocaleKeys.grid_checklistFilter_isComplete.tr()),
  754. );
  755. await tapButton(button);
  756. }
  757. Future<void> tapUnCompletedButtonOnChecklistFilter() async {
  758. final button = find.descendant(
  759. of: find.byType(HoverButton),
  760. matching: find.text(LocaleKeys.grid_checklistFilter_isIncomplted.tr()),
  761. );
  762. await tapButton(button);
  763. }
  764. /// Should call [tapDatabaseSettingButton] first.
  765. Future<void> tapDatabaseLayoutButton() async {
  766. final findSettingItem = find.byType(DatabaseSettingItem);
  767. final findLayoutButton = find.byWidgetPredicate(
  768. (widget) =>
  769. widget is FlowyText &&
  770. widget.text == DatabaseSettingAction.showLayout.title(),
  771. );
  772. final button = find.descendant(
  773. of: findSettingItem,
  774. matching: findLayoutButton,
  775. );
  776. await tapButton(button);
  777. }
  778. Future<void> tapCalendarLayoutSettingButton() async {
  779. final findSettingItem = find.byType(DatabaseSettingItem);
  780. final findLayoutButton = find.byWidgetPredicate(
  781. (widget) =>
  782. widget is FlowyText &&
  783. widget.text == DatabaseSettingAction.showCalendarLayout.title(),
  784. );
  785. final button = find.descendant(
  786. of: findSettingItem,
  787. matching: findLayoutButton,
  788. );
  789. await tapButton(button);
  790. }
  791. Future<void> tapFirstDayOfWeek() async {
  792. await tapButton(find.byType(FirstDayOfWeek));
  793. }
  794. Future<void> tapFirstDayOfWeekStartFromSunday() async {
  795. final finder = find.byWidgetPredicate(
  796. (widget) => widget is StartFromButton && widget.dayIndex == 0,
  797. );
  798. await tapButton(finder);
  799. }
  800. Future<void> tapFirstDayOfWeekStartFromMonday() async {
  801. final finder = find.byWidgetPredicate(
  802. (widget) => widget is StartFromButton && widget.dayIndex == 1,
  803. );
  804. await tapButton(finder);
  805. // Dismiss the popover overlay in cause of obscure the tapButton
  806. // in the next test case.
  807. await sendKeyEvent(LogicalKeyboardKey.escape);
  808. await pumpAndSettle(const Duration(milliseconds: 200));
  809. }
  810. void assertFirstDayOfWeekStartFromMonday() {
  811. final finder = find.byWidgetPredicate(
  812. (widget) =>
  813. widget is StartFromButton &&
  814. widget.dayIndex == 1 &&
  815. widget.isSelected == true,
  816. );
  817. expect(finder, findsOneWidget);
  818. }
  819. void assertFirstDayOfWeekStartFromSunday() {
  820. final finder = find.byWidgetPredicate(
  821. (widget) =>
  822. widget is StartFromButton &&
  823. widget.dayIndex == 0 &&
  824. widget.isSelected == true,
  825. );
  826. expect(finder, findsOneWidget);
  827. }
  828. Future<void> scrollToToday() async {
  829. final todayCell = find.byWidgetPredicate(
  830. (widget) => widget is CalendarDayCard && widget.isToday,
  831. skipOffstage: false,
  832. );
  833. await ensureVisible(todayCell);
  834. await pumpAndSettle(const Duration(milliseconds: 300));
  835. }
  836. Future<void> hoverOnTodayCalendarCell() async {
  837. final todayCell = find.byWidgetPredicate(
  838. (widget) => widget is CalendarDayCard && widget.isToday,
  839. );
  840. await hoverOnWidget(todayCell);
  841. }
  842. Future<void> tapAddCalendarEventButton() async {
  843. final findFlowyButton = find.byType(FlowyIconButton);
  844. final findNewEventButton = find.byType(NewEventButton);
  845. final button = find.descendant(
  846. of: findNewEventButton,
  847. matching: findFlowyButton,
  848. );
  849. await tapButton(button);
  850. }
  851. /// Checks for a certain number of events. Parameters [date] and [title] can
  852. /// also be provided to restrict the scope of the search
  853. void assertNumberOfEventsInCalendar(int number, {String? title}) {
  854. Finder findEvents = find.byType(EventCard);
  855. if (title != null) {
  856. findEvents = find.descendant(of: findEvents, matching: find.text(title));
  857. }
  858. expect(findEvents, findsNWidgets(number));
  859. }
  860. void assertNumberofEventsOnSpecificDay(
  861. int number,
  862. DateTime date, {
  863. String? title,
  864. }) {
  865. final findDayCell = find.byWidgetPredicate(
  866. (widget) =>
  867. widget is CalendarDayCard &&
  868. isSameDay(
  869. widget.date,
  870. date,
  871. ),
  872. );
  873. Finder findEvents = find.descendant(
  874. of: findDayCell,
  875. matching: find.byType(EventCard),
  876. );
  877. if (title != null) {
  878. findEvents = find.descendant(of: findEvents, matching: find.text(title));
  879. }
  880. expect(findEvents, findsNWidgets(number));
  881. }
  882. Future<void> doubleClickCalendarCell(DateTime date) async {
  883. final todayCell = find.byWidgetPredicate(
  884. (widget) => widget is CalendarDayCard && isSameDay(date, widget.date),
  885. );
  886. await doubleTapButton(todayCell);
  887. }
  888. Future<void> openCalendarEvent({required index, DateTime? date}) async {
  889. final findDayCell = find.byWidgetPredicate(
  890. (widget) =>
  891. widget is CalendarDayCard &&
  892. isSameDay(widget.date, date ?? DateTime.now()),
  893. );
  894. final cards = find.descendant(
  895. of: findDayCell,
  896. matching: find.byType(EventCard),
  897. );
  898. await tapButton(cards.at(index));
  899. }
  900. Future<void> dragDropRescheduleCalendarEvent(DateTime startDate) async {
  901. final findEventCard = find.byType(EventCard);
  902. await drag(findEventCard.first, const Offset(0, 300));
  903. await pumpAndSettle();
  904. }
  905. Future<void> tapCreateLinkedDatabaseViewButton(AddButtonAction action) async {
  906. final findAddButton = find.byType(AddDatabaseViewButton);
  907. await tapButton(findAddButton);
  908. final findCreateButton = find.byWidgetPredicate(
  909. (widget) =>
  910. widget is TarBarAddButtonActionCell && widget.action == action,
  911. );
  912. await tapButton(findCreateButton);
  913. }
  914. Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) {
  915. return find.byWidgetPredicate(
  916. (widget) => widget is TabBarItemButton && widget.view.layout == layout,
  917. );
  918. }
  919. Finder findTabBarLinkViewByViewName(String name) {
  920. return find.byWidgetPredicate(
  921. (widget) => widget is TabBarItemButton && widget.view.name == name,
  922. );
  923. }
  924. Future<void> renameLinkedView(Finder linkedView, String name) async {
  925. await tap(linkedView, buttons: kSecondaryButton);
  926. await pumpAndSettle();
  927. await tapButton(
  928. find.byWidgetPredicate(
  929. (widget) =>
  930. widget is ActionCellWidget &&
  931. widget.action == TabBarViewAction.rename,
  932. ),
  933. );
  934. await enterText(
  935. find.descendant(
  936. of: find.byType(FlowyFormTextInput),
  937. matching: find.byType(TextFormField),
  938. ),
  939. name,
  940. );
  941. final field = find.byWidgetPredicate(
  942. (widget) =>
  943. widget is PrimaryTextButton &&
  944. widget.label == LocaleKeys.button_OK.tr(),
  945. );
  946. await tapButton(field);
  947. }
  948. Future<void> deleteDatebaseView(Finder linkedView) async {
  949. await tap(linkedView, buttons: kSecondaryButton);
  950. await pumpAndSettle();
  951. await tapButton(
  952. find.byWidgetPredicate(
  953. (widget) =>
  954. widget is ActionCellWidget &&
  955. widget.action == TabBarViewAction.delete,
  956. ),
  957. );
  958. final okButton = find.byWidgetPredicate(
  959. (widget) =>
  960. widget is PrimaryTextButton &&
  961. widget.label == LocaleKeys.button_OK.tr(),
  962. );
  963. await tapButton(okButton);
  964. }
  965. Future<void> assertCurrentDatabaseTagIs(DatabaseLayoutPB layout) async {
  966. switch (layout) {
  967. case DatabaseLayoutPB.Board:
  968. expect(find.byType(BoardPage), findsOneWidget);
  969. break;
  970. case DatabaseLayoutPB.Calendar:
  971. expect(find.byType(CalendarPage), findsOneWidget);
  972. break;
  973. case DatabaseLayoutPB.Grid:
  974. expect(find.byType(GridPage), findsOneWidget);
  975. break;
  976. default:
  977. throw Exception('Unknown database layout type: $layout');
  978. }
  979. }
  980. Future<void> selectDatabaseLayoutType(DatabaseLayoutPB layout) async {
  981. final findLayoutCell = find.byType(DatabaseViewLayoutCell);
  982. final findText = find.byWidgetPredicate(
  983. (widget) => widget is FlowyText && widget.text == layout.layoutName(),
  984. );
  985. final button = find.descendant(
  986. of: findLayoutCell,
  987. matching: findText,
  988. );
  989. await tapButton(button);
  990. }
  991. Future<void> assertCurrentDatabaseLayoutType(DatabaseLayoutPB layout) async {
  992. expect(finderForDatabaseLayoutType(layout), findsOneWidget);
  993. }
  994. Future<void> tapDatabaseRawDataButton() async {
  995. await tapButtonWithName(LocaleKeys.importPanel_database.tr());
  996. }
  997. }
  998. Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
  999. switch (layout) {
  1000. case DatabaseLayoutPB.Board:
  1001. return find.byType(BoardPage);
  1002. case DatabaseLayoutPB.Calendar:
  1003. return find.byType(CalendarPage);
  1004. case DatabaseLayoutPB.Grid:
  1005. return find.byType(GridPage);
  1006. default:
  1007. throw Exception('Unknown database layout type: $layout');
  1008. }
  1009. }
  1010. Finder finderForFieldType(FieldType fieldType) {
  1011. switch (fieldType) {
  1012. case FieldType.Checkbox:
  1013. return find.byType(GridCheckboxCell, skipOffstage: false);
  1014. case FieldType.DateTime:
  1015. return find.byType(GridDateCell, skipOffstage: false);
  1016. case FieldType.LastEditedTime:
  1017. case FieldType.CreatedTime:
  1018. return find.byType(GridDateCell, skipOffstage: false);
  1019. case FieldType.SingleSelect:
  1020. return find.byType(GridSingleSelectCell, skipOffstage: false);
  1021. case FieldType.MultiSelect:
  1022. return find.byType(GridMultiSelectCell, skipOffstage: false);
  1023. case FieldType.Checklist:
  1024. return find.byType(GridChecklistCell, skipOffstage: false);
  1025. case FieldType.Number:
  1026. return find.byType(GridNumberCell, skipOffstage: false);
  1027. case FieldType.RichText:
  1028. return find.byType(GridTextCell, skipOffstage: false);
  1029. case FieldType.URL:
  1030. return find.byType(GridURLCell, skipOffstage: false);
  1031. default:
  1032. throw Exception('Unknown field type: $fieldType');
  1033. }
  1034. }