database_test_op.dart 45 KB

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