database_test_op.dart 38 KB

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