database_test_op.dart 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527
  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_event_editor.dart';
  9. import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
  10. import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
  11. import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
  12. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
  13. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart';
  14. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart';
  15. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart';
  16. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart';
  17. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart';
  18. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart';
  19. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart';
  20. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart';
  21. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
  22. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart';
  23. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
  24. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
  25. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart';
  26. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
  27. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
  28. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/timestamp.dart';
  29. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart';
  30. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart';
  31. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/order_panel.dart';
  32. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart';
  33. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_menu.dart';
  34. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
  35. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart';
  36. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart';
  37. import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_header.dart';
  38. import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
  39. import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
  40. import 'package:appflowy/plugins/database_view/widgets/field/grid_property.dart';
  41. import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
  42. import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
  43. import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart';
  44. import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
  45. import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart';
  46. import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
  47. import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart';
  48. import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.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. Future<void> createNewChecklistTask({
  436. required String name,
  437. enter = false,
  438. button = false,
  439. }) async {
  440. assert(!(enter && button));
  441. final textField = find.descendant(
  442. of: find.byType(NewTaskItem),
  443. matching: find.byType(TextField),
  444. );
  445. await enterText(textField, name);
  446. await pumpAndSettle();
  447. if (enter) {
  448. await testTextInput.receiveAction(TextInputAction.done);
  449. await pumpAndSettle();
  450. } else {
  451. await tapButton(
  452. find.descendant(
  453. of: find.byType(NewTaskItem),
  454. matching: find.byType(FlowyTextButton),
  455. ),
  456. );
  457. }
  458. }
  459. void assertChecklistTaskInEditor({
  460. required int index,
  461. required String name,
  462. required bool isChecked,
  463. }) {
  464. final task = find.byType(ChecklistItem).at(index);
  465. final widget = this.widget<ChecklistItem>(task);
  466. assert(
  467. widget.option.data.name == name && widget.option.isSelected == isChecked,
  468. );
  469. }
  470. Future<void> renameChecklistTask({
  471. required int index,
  472. required String name,
  473. }) async {
  474. final textField = find
  475. .descendant(
  476. of: find.byType(ChecklistItem),
  477. matching: find.byType(TextField),
  478. )
  479. .at(index);
  480. await enterText(textField, name);
  481. await testTextInput.receiveAction(TextInputAction.done);
  482. await pumpAndSettle();
  483. }
  484. Future<void> checkChecklistTask({required int index}) async {
  485. final button = find.descendant(
  486. of: find.byType(ChecklistItem).at(index),
  487. matching: find.byWidgetPredicate(
  488. (widget) => widget is FlowySvg && widget.svg == FlowySvgs.uncheck_s,
  489. ),
  490. );
  491. await tapButton(button);
  492. }
  493. Future<void> deleteChecklistTask({required int index}) async {
  494. final task = find.byType(ChecklistItem).at(index);
  495. await startGesture(getCenter(task), kind: PointerDeviceKind.mouse);
  496. await pumpAndSettle();
  497. final button = find.byWidgetPredicate(
  498. (widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s,
  499. );
  500. await tapButton(button);
  501. }
  502. Future<void> openFirstRowDetailPage() async {
  503. await hoverOnFirstRowOfGrid();
  504. final expandButton = find.byType(PrimaryCellAccessory);
  505. expect(expandButton, findsOneWidget);
  506. await tapButton(expandButton);
  507. }
  508. void assertRowDetailPageOpened() async {
  509. final findRowDetailPage = find.byType(RowDetailPage);
  510. expect(findRowDetailPage, findsOneWidget);
  511. }
  512. Future<void> dismissRowDetailPage() async {
  513. // use tap empty area instead of clicking ESC to dismiss the row detail page
  514. // sometimes, the ESC key is not working.
  515. await simulateKeyEvent(LogicalKeyboardKey.escape);
  516. await pumpAndSettle();
  517. final findRowDetailPage = find.byType(RowDetailPage);
  518. if (findRowDetailPage.evaluate().isNotEmpty) {
  519. await tapAt(const Offset(0, 0));
  520. await pumpAndSettle();
  521. }
  522. }
  523. Future<void> editTitleInRowDetailPage(String title) async {
  524. final titleField = find.byType(GridTextCell);
  525. await enterText(titleField, title);
  526. await pumpAndSettle();
  527. }
  528. Future<void> hoverRowBanner() async {
  529. final banner = find.byType(RowBanner);
  530. expect(banner, findsOneWidget);
  531. await startGesture(
  532. getCenter(banner),
  533. kind: PointerDeviceKind.mouse,
  534. );
  535. await pumpAndSettle();
  536. }
  537. Future<void> openEmojiPicker() async {
  538. await tapButton(find.byType(EmojiPickerButton));
  539. await tapButton(find.byType(EmojiSelectionMenu));
  540. }
  541. Future<void> tapDateCellInRowDetailPage() async {
  542. final findDateCell = find.byType(GridDateCell);
  543. await tapButton(findDateCell);
  544. }
  545. Future<void> tapGridFieldWithNameInRowDetailPage(String name) async {
  546. final fields = find.byWidgetPredicate(
  547. (widget) => widget is FieldCellButton && widget.field.name == name,
  548. );
  549. final field = find.descendant(
  550. of: find.byType(RowDetailPage),
  551. matching: fields,
  552. );
  553. await tapButton(field);
  554. await pumpAndSettle();
  555. }
  556. Future<void> duplicateRowInRowDetailPage() async {
  557. final duplicateButton = find.byType(RowDetailPageDuplicateButton);
  558. await tapButton(duplicateButton);
  559. }
  560. Future<void> deleteRowInRowDetailPage() async {
  561. final deleteButton = find.byType(RowDetailPageDeleteButton);
  562. await tapButton(deleteButton);
  563. }
  564. Future<TestGesture> hoverOnFieldInRowDetail({required int index}) async {
  565. final fieldButtons = find.byType(FieldCellButton);
  566. final button = find
  567. .descendant(of: find.byType(RowDetailPage), matching: fieldButtons)
  568. .at(index);
  569. return startGesture(
  570. getCenter(button),
  571. kind: PointerDeviceKind.mouse,
  572. );
  573. }
  574. Future<void> reorderFieldInRowDetail({required double offset}) async {
  575. final thumb = find
  576. .byWidgetPredicate(
  577. (widget) => widget is ReorderableDragStartListener && widget.enabled,
  578. )
  579. .first;
  580. await drag(
  581. thumb,
  582. Offset(0, offset),
  583. kind: PointerDeviceKind.mouse,
  584. );
  585. await pumpAndSettle();
  586. }
  587. Future<void> scrollGridByOffset(Offset offset) async {
  588. await drag(find.byType(GridPage), offset);
  589. await pumpAndSettle();
  590. }
  591. Future<void> scrollRowDetailByOffset(Offset offset) async {
  592. await drag(find.byType(RowDetailPage), offset);
  593. await pumpAndSettle();
  594. }
  595. Future<void> scrollToRight(Finder find) async {
  596. final size = getSize(find);
  597. await drag(find, Offset(-size.width, 0));
  598. await pumpAndSettle(const Duration(milliseconds: 500));
  599. }
  600. Future<void> tapNewPropertyButton() async {
  601. await tapButtonWithName(LocaleKeys.grid_field_newProperty.tr());
  602. await pumpAndSettle();
  603. }
  604. Future<void> tapGridFieldWithName(String name) async {
  605. final field = find.byWidgetPredicate(
  606. (widget) => widget is FieldCellButton && widget.field.name == name,
  607. );
  608. await tapButton(field);
  609. await pumpAndSettle();
  610. }
  611. /// Should call [tapGridFieldWithName] first.
  612. Future<void> tapEditPropertyButton() async {
  613. await tapButtonWithName(LocaleKeys.grid_field_editProperty.tr());
  614. await pumpAndSettle(const Duration(milliseconds: 200));
  615. }
  616. /// Should call [tapGridFieldWithName] first.
  617. Future<void> tapDeletePropertyButton() async {
  618. final field = find.byWidgetPredicate(
  619. (widget) =>
  620. widget is FieldActionCell && widget.action == FieldAction.delete,
  621. );
  622. await tapButton(field);
  623. }
  624. /// Should call [tapGridFieldWithName] first.
  625. Future<void> tapDialogOkButton() async {
  626. final field = find.byWidgetPredicate(
  627. (widget) =>
  628. widget is PrimaryTextButton &&
  629. widget.label == LocaleKeys.button_OK.tr(),
  630. );
  631. await tapButton(field);
  632. }
  633. /// Should call [tapGridFieldWithName] first.
  634. Future<void> tapDuplicatePropertyButton() async {
  635. final field = find.byWidgetPredicate(
  636. (widget) =>
  637. widget is FieldActionCell && widget.action == FieldAction.duplicate,
  638. );
  639. await tapButton(field);
  640. }
  641. /// Should call [tapGridFieldWithName] first.
  642. Future<void> tapHidePropertyButton() async {
  643. final field = find.byWidgetPredicate(
  644. (widget) =>
  645. widget is FieldActionCell && widget.action == FieldAction.hide,
  646. );
  647. await tapButton(field);
  648. }
  649. Future<void> tapHidePropertyButtonInFieldEditor() async {
  650. final button = find.byType(HideFieldButton);
  651. await tapButton(button);
  652. }
  653. Future<void> tapRowDetailPageRowActionButton() async {
  654. await tapButton(find.byType(RowActionButton));
  655. }
  656. Future<void> tapRowDetailPageCreatePropertyButton() async {
  657. await tapButton(find.byType(CreateRowFieldButton));
  658. }
  659. Future<void> tapRowDetailPageDeleteRowButton() async {
  660. await tapButton(find.byType(RowDetailPageDeleteButton));
  661. }
  662. Future<void> tapRowDetailPageDuplicateRowButton() async {
  663. await tapButton(find.byType(RowDetailPageDuplicateButton));
  664. }
  665. Future<void> tapTypeOptionButton() async {
  666. await tapButton(find.byType(SwitchFieldButton));
  667. }
  668. Future<void> tapEscButton() async {
  669. await sendKeyEvent(LogicalKeyboardKey.escape);
  670. }
  671. /// Must call [tapTypeOptionButton] first.
  672. Future<void> selectFieldType(FieldType fieldType) async {
  673. final fieldTypeCell = find.byType(FieldTypeCell);
  674. final fieldTypeButton = find.descendant(
  675. of: fieldTypeCell,
  676. matching: find.byWidgetPredicate(
  677. (widget) => widget is FlowyText && widget.text == fieldType.title(),
  678. ),
  679. );
  680. await tapButton(fieldTypeButton);
  681. }
  682. /// Each field has its own cell, so we can find the corresponding cell by
  683. /// the field type after create a new field.
  684. Future<void> findCellByFieldType(FieldType fieldType) async {
  685. final finder = finderForFieldType(fieldType);
  686. expect(finder, findsWidgets);
  687. }
  688. Future<void> assertNumberOfFieldsInGridPage(int num) async {
  689. expect(find.byType(GridFieldCell), findsNWidgets(num));
  690. }
  691. Future<void> assertNumberOfRowsInGridPage(int num) async {
  692. expect(
  693. find.byType(GridRow, skipOffstage: false),
  694. findsNWidgets(num),
  695. );
  696. }
  697. Future<void> assertDocumentExistInRowDetailPage() async {
  698. expect(find.byType(RowDocument), findsOneWidget);
  699. }
  700. /// Check the field type of the [FieldCellButton] is the same as the name.
  701. Future<void> assertFieldTypeWithFieldName(
  702. String name,
  703. FieldType fieldType,
  704. ) async {
  705. final field = find.byWidgetPredicate(
  706. (widget) =>
  707. widget is FieldCellButton &&
  708. widget.field.fieldType == fieldType &&
  709. widget.field.name == name,
  710. );
  711. expect(field, findsOneWidget);
  712. }
  713. void assertFirstFieldInRowDetailByType(FieldType fieldType) {
  714. final firstField = find
  715. .descendant(
  716. of: find.byType(RowDetailPage),
  717. matching: find.byType(FieldCellButton),
  718. )
  719. .first;
  720. final widget = this.widget<FieldCellButton>(firstField);
  721. expect(widget.field.fieldType, fieldType);
  722. }
  723. Future<void> findFieldWithName(String name) async {
  724. final field = find.byWidgetPredicate(
  725. (widget) => widget is FieldCellButton && widget.field.name == name,
  726. );
  727. expect(field, findsOneWidget);
  728. }
  729. Future<void> noFieldWithName(String name) async {
  730. final field = find.byWidgetPredicate(
  731. (widget) => widget is FieldCellButton && widget.field.name == name,
  732. );
  733. expect(field, findsNothing);
  734. }
  735. Future<void> renameField(String newName) async {
  736. final textField = find.byType(FieldNameTextField);
  737. expect(textField, findsOneWidget);
  738. await enterText(textField, newName);
  739. await pumpAndSettle();
  740. }
  741. Future<void> dismissFieldEditor() async {
  742. await sendKeyEvent(LogicalKeyboardKey.escape);
  743. await pumpAndSettle(const Duration(milliseconds: 200));
  744. }
  745. Future<void> findFieldEditor(dynamic matcher) async {
  746. final finder = find.byType(FieldEditor);
  747. expect(finder, matcher);
  748. }
  749. Future<void> findDateEditor(dynamic matcher) async {
  750. final finder = find.byType(DateCellEditor);
  751. expect(finder, matcher);
  752. }
  753. Future<void> findSelectOptionEditor(dynamic matcher) async {
  754. final finder = find.byType(SelectOptionCellEditor);
  755. expect(finder, matcher);
  756. }
  757. Future<void> dismissCellEditor() async {
  758. await sendKeyEvent(LogicalKeyboardKey.escape);
  759. await pumpAndSettle();
  760. }
  761. Future<void> tapCreateRowButtonInGrid() async {
  762. await tapButton(find.byType(GridAddRowButton));
  763. }
  764. Future<void> tapCreateRowButtonInRowMenuOfGrid() async {
  765. await tapButton(find.byType(InsertRowButton));
  766. }
  767. Future<void> tapRowMenuButtonInGrid() async {
  768. await tapButton(find.byType(RowMenuButton));
  769. }
  770. /// Should call [tapRowMenuButtonInGrid] first.
  771. Future<void> tapDeleteOnRowMenu() async {
  772. await tapButtonWithName(LocaleKeys.grid_row_delete.tr());
  773. }
  774. Future<void> assertRowCountInGridPage(int num) async {
  775. final text = find.text('${rowCountString()} $num',findRichText: true);
  776. expect(text, findsOneWidget);
  777. }
  778. Future<void> createField(FieldType fieldType, String name) async {
  779. await scrollToRight(find.byType(GridPage));
  780. await tapNewPropertyButton();
  781. await renameField(name);
  782. await tapTypeOptionButton();
  783. await selectFieldType(fieldType);
  784. await dismissFieldEditor();
  785. }
  786. Future<void> tapDatabaseSettingButton() async {
  787. await tapButton(find.byType(SettingButton));
  788. }
  789. Future<void> tapDatabaseFilterButton() async {
  790. await tapButton(find.byType(FilterButton));
  791. }
  792. Future<void> tapDatabaseSortButton() async {
  793. await tapButton(find.byType(SortButton));
  794. }
  795. Future<void> tapCreateFilterByFieldType(
  796. FieldType fieldType,
  797. String title,
  798. ) async {
  799. final findFilter = find.byWidgetPredicate(
  800. (widget) =>
  801. widget is GridFilterPropertyCell &&
  802. widget.fieldInfo.fieldType == fieldType &&
  803. widget.fieldInfo.name == title,
  804. );
  805. await tapButton(findFilter);
  806. }
  807. Future<void> tapFilterButtonInGrid(String filterName) async {
  808. final findFilter = find.byType(FilterMenuItem);
  809. final button = find.descendant(
  810. of: findFilter,
  811. matching: find.text(filterName),
  812. );
  813. await tapButton(button);
  814. }
  815. Future<void> tapCreateSortByFieldType(
  816. FieldType fieldType,
  817. String title,
  818. ) async {
  819. final findSort = find.byWidgetPredicate(
  820. (widget) =>
  821. widget is GridSortPropertyCell &&
  822. widget.fieldInfo.fieldType == fieldType &&
  823. widget.fieldInfo.name == title,
  824. );
  825. await tapButton(findSort);
  826. }
  827. // Must call [tapSortMenuInSettingBar] first.
  828. Future<void> tapCreateSortByFieldTypeInSortMenu(
  829. FieldType fieldType,
  830. String title,
  831. ) async {
  832. await tapButton(find.byType(DatabaseAddSortButton));
  833. final findSort = find.byWidgetPredicate(
  834. (widget) =>
  835. widget is GridSortPropertyCell &&
  836. widget.fieldInfo.fieldType == fieldType &&
  837. widget.fieldInfo.name == title,
  838. );
  839. await tapButton(findSort);
  840. await pumpAndSettle();
  841. }
  842. Future<void> tapSortMenuInSettingBar() async {
  843. await tapButton(find.byType(SortMenu));
  844. await pumpAndSettle();
  845. }
  846. /// Must call [tapSortMenuInSettingBar] first.
  847. Future<void> tapSortButtonByName(String name) async {
  848. final findSortItem = find.byWidgetPredicate(
  849. (widget) =>
  850. widget is DatabaseSortItem && widget.sortInfo.fieldInfo.name == name,
  851. );
  852. await tapButton(findSortItem);
  853. }
  854. /// Must call [tapSortButtonByName] first.
  855. Future<void> tapSortByDescending() async {
  856. await tapButton(
  857. find.descendant(
  858. of: find.byType(OrderPannelItem),
  859. matching: find.byWidgetPredicate(
  860. (widget) =>
  861. widget is FlowyText &&
  862. widget.text == LocaleKeys.grid_sort_descending.tr(),
  863. ),
  864. ),
  865. );
  866. await sendKeyEvent(LogicalKeyboardKey.escape);
  867. await pumpAndSettle();
  868. }
  869. /// Must call [tapSortMenuInSettingBar] first.
  870. Future<void> tapAllSortButton() async {
  871. await tapButton(find.byType(DatabaseDeleteSortButton));
  872. }
  873. Future<void> scrollOptionFilterListByOffset(Offset offset) async {
  874. await drag(find.byType(SelectOptionFilterEditor), offset);
  875. await pumpAndSettle();
  876. }
  877. Future<void> enterTextInTextFilter(String text) async {
  878. final findEditor = find.byType(TextFilterEditor);
  879. final findTextField = find.descendant(
  880. of: findEditor,
  881. matching: find.byType(FlowyTextField),
  882. );
  883. await enterText(findTextField, text);
  884. await pumpAndSettle(const Duration(milliseconds: 300));
  885. }
  886. Future<void> tapDisclosureButtonInFinder(Finder finder) async {
  887. final findDisclosure = find.descendant(
  888. of: finder,
  889. matching: find.byType(DisclosureButton),
  890. );
  891. await tapButton(findDisclosure);
  892. }
  893. /// must call [tapDisclosureButtonInFinder] first.
  894. Future<void> tapDeleteFilterButtonInGrid() async {
  895. await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr()));
  896. }
  897. Future<void> tapCheckboxFilterButtonInGrid() async {
  898. await tapButton(find.byType(CheckboxFilterConditionList));
  899. }
  900. Future<void> tapChecklistFilterButtonInGrid() async {
  901. await tapButton(find.byType(ChecklistFilterConditionList));
  902. }
  903. /// The [SelectOptionFilterList] must show up first.
  904. Future<void> tapOptionFilterWithName(String name) async {
  905. final findCell = find.descendant(
  906. of: find.byType(SelectOptionFilterList),
  907. matching: find.byWidgetPredicate(
  908. (widget) =>
  909. widget is SelectOptionFilterCell && widget.option.name == name,
  910. skipOffstage: false,
  911. ),
  912. skipOffstage: false,
  913. );
  914. expect(findCell, findsOneWidget);
  915. await tapButton(findCell, warnIfMissed: false);
  916. }
  917. Future<void> tapCheckedButtonOnCheckboxFilter() async {
  918. final button = find.descendant(
  919. of: find.byType(HoverButton),
  920. matching: find.text(LocaleKeys.grid_checkboxFilter_isChecked.tr()),
  921. );
  922. await tapButton(button);
  923. }
  924. Future<void> tapUnCheckedButtonOnCheckboxFilter() async {
  925. final button = find.descendant(
  926. of: find.byType(HoverButton),
  927. matching: find.text(LocaleKeys.grid_checkboxFilter_isUnchecked.tr()),
  928. );
  929. await tapButton(button);
  930. }
  931. Future<void> tapCompletedButtonOnChecklistFilter() async {
  932. final button = find.descendant(
  933. of: find.byType(HoverButton),
  934. matching: find.text(LocaleKeys.grid_checklistFilter_isComplete.tr()),
  935. );
  936. await tapButton(button);
  937. }
  938. Future<void> tapUnCompletedButtonOnChecklistFilter() async {
  939. final button = find.descendant(
  940. of: find.byType(HoverButton),
  941. matching: find.text(LocaleKeys.grid_checklistFilter_isIncomplted.tr()),
  942. );
  943. await tapButton(button);
  944. }
  945. /// Should call [tapDatabaseSettingButton] first.
  946. Future<void> tapViewPropertiesButton() async {
  947. final findSettingItem = find.byType(DatabaseSettingItem);
  948. final findLayoutButton = find.byWidgetPredicate(
  949. (widget) =>
  950. widget is FlowyText &&
  951. widget.text == DatabaseSettingAction.showProperties.title(),
  952. );
  953. final button = find.descendant(
  954. of: findSettingItem,
  955. matching: findLayoutButton,
  956. );
  957. await tapButton(button);
  958. }
  959. /// Should call [tapDatabaseSettingButton] first.
  960. Future<void> tapDatabaseLayoutButton() async {
  961. final findSettingItem = find.byType(DatabaseSettingItem);
  962. final findLayoutButton = find.byWidgetPredicate(
  963. (widget) =>
  964. widget is FlowyText &&
  965. widget.text == DatabaseSettingAction.showLayout.title(),
  966. );
  967. final button = find.descendant(
  968. of: findSettingItem,
  969. matching: findLayoutButton,
  970. );
  971. await tapButton(button);
  972. }
  973. Future<void> tapCalendarLayoutSettingButton() async {
  974. final findSettingItem = find.byType(DatabaseSettingItem);
  975. final findLayoutButton = find.byWidgetPredicate(
  976. (widget) =>
  977. widget is FlowyText &&
  978. widget.text == DatabaseSettingAction.showCalendarLayout.title(),
  979. );
  980. final button = find.descendant(
  981. of: findSettingItem,
  982. matching: findLayoutButton,
  983. );
  984. await tapButton(button);
  985. }
  986. Future<void> tapFirstDayOfWeek() async {
  987. await tapButton(find.byType(FirstDayOfWeek));
  988. }
  989. Future<void> tapFirstDayOfWeekStartFromSunday() async {
  990. final finder = find.byWidgetPredicate(
  991. (widget) => widget is StartFromButton && widget.dayIndex == 0,
  992. );
  993. await tapButton(finder);
  994. }
  995. Future<void> tapFirstDayOfWeekStartFromMonday() async {
  996. final finder = find.byWidgetPredicate(
  997. (widget) => widget is StartFromButton && widget.dayIndex == 1,
  998. );
  999. await tapButton(finder);
  1000. // Dismiss the popover overlay in cause of obscure the tapButton
  1001. // in the next test case.
  1002. await sendKeyEvent(LogicalKeyboardKey.escape);
  1003. await pumpAndSettle(const Duration(milliseconds: 200));
  1004. }
  1005. void assertFirstDayOfWeekStartFromMonday() {
  1006. final finder = find.byWidgetPredicate(
  1007. (widget) =>
  1008. widget is StartFromButton &&
  1009. widget.dayIndex == 1 &&
  1010. widget.isSelected == true,
  1011. );
  1012. expect(finder, findsOneWidget);
  1013. }
  1014. void assertFirstDayOfWeekStartFromSunday() {
  1015. final finder = find.byWidgetPredicate(
  1016. (widget) =>
  1017. widget is StartFromButton &&
  1018. widget.dayIndex == 0 &&
  1019. widget.isSelected == true,
  1020. );
  1021. expect(finder, findsOneWidget);
  1022. }
  1023. Future<void> scrollToToday() async {
  1024. final todayCell = find.byWidgetPredicate(
  1025. (widget) => widget is CalendarDayCard && widget.isToday,
  1026. );
  1027. final scrollable = find
  1028. .descendant(
  1029. of: find.byType(MonthView<CalendarDayEvent>),
  1030. matching: find.byWidgetPredicate(
  1031. (widget) => widget is Scrollable && widget.axis == Axis.vertical,
  1032. ),
  1033. )
  1034. .first;
  1035. await scrollUntilVisible(
  1036. todayCell,
  1037. 300,
  1038. scrollable: scrollable,
  1039. );
  1040. await pumpAndSettle(const Duration(milliseconds: 300));
  1041. }
  1042. Future<void> hoverOnTodayCalendarCell() async {
  1043. final todayCell = find.byWidgetPredicate(
  1044. (widget) => widget is CalendarDayCard && widget.isToday,
  1045. );
  1046. await hoverOnWidget(todayCell);
  1047. }
  1048. Future<void> tapAddCalendarEventButton() async {
  1049. final findFlowyButton = find.byType(FlowyIconButton);
  1050. final findNewEventButton = find.byType(NewEventButton);
  1051. final button = find.descendant(
  1052. of: findNewEventButton,
  1053. matching: findFlowyButton,
  1054. );
  1055. await tapButton(button);
  1056. }
  1057. /// Checks for a certain number of events. Parameters [date] and [title] can
  1058. /// also be provided to restrict the scope of the search
  1059. void assertNumberOfEventsInCalendar(int number, {String? title}) {
  1060. Finder findEvents = find.byType(EventCard);
  1061. if (title != null) {
  1062. findEvents = find.descendant(of: findEvents, matching: find.text(title));
  1063. }
  1064. expect(findEvents, findsNWidgets(number));
  1065. }
  1066. void assertNumberOfEventsOnSpecificDay(
  1067. int number,
  1068. DateTime date, {
  1069. String? title,
  1070. }) {
  1071. final findDayCell = find.byWidgetPredicate(
  1072. (widget) =>
  1073. widget is CalendarDayCard &&
  1074. isSameDay(
  1075. widget.date,
  1076. date,
  1077. ),
  1078. );
  1079. Finder findEvents = find.descendant(
  1080. of: findDayCell,
  1081. matching: find.byType(EventCard),
  1082. );
  1083. if (title != null) {
  1084. findEvents = find.descendant(of: findEvents, matching: find.text(title));
  1085. }
  1086. expect(findEvents, findsNWidgets(number));
  1087. }
  1088. Future<void> doubleClickCalendarCell(DateTime date) async {
  1089. final todayCell = find.byWidgetPredicate(
  1090. (widget) => widget is CalendarDayCard && isSameDay(date, widget.date),
  1091. );
  1092. final location = getTopLeft(todayCell).translate(10, 10);
  1093. await doubleTapAt(location);
  1094. }
  1095. Future<void> openCalendarEvent({required index, DateTime? date}) async {
  1096. final findDayCell = find.byWidgetPredicate(
  1097. (widget) =>
  1098. widget is CalendarDayCard &&
  1099. isSameDay(widget.date, date ?? DateTime.now()),
  1100. );
  1101. final cards = find.descendant(
  1102. of: findDayCell,
  1103. matching: find.byType(EventCard),
  1104. );
  1105. await tapButton(cards.at(index));
  1106. }
  1107. void assertEventEditorOpen() {
  1108. expect(find.byType(CalendarEventEditor), findsOneWidget);
  1109. }
  1110. Future<void> dismissEventEditor() async {
  1111. await simulateKeyEvent(LogicalKeyboardKey.escape);
  1112. }
  1113. Future<void> editEventTitle(String title) async {
  1114. final textField = find.descendant(
  1115. of: find.byType(CalendarEventEditor),
  1116. matching: find.byType(FlowyTextField),
  1117. );
  1118. await enterText(textField, title);
  1119. await pumpAndSettle(const Duration(milliseconds: 300));
  1120. }
  1121. Future<void> openEventToRowDetailPage() async {
  1122. final button = find.descendant(
  1123. of: find.byType(CalendarEventEditor),
  1124. matching: find.byWidgetPredicate(
  1125. (widget) => widget is FlowySvg && widget.svg == FlowySvgs.full_view_s,
  1126. ),
  1127. );
  1128. await tapButton(button);
  1129. }
  1130. Future<void> deleteEventFromEventEditor() async {
  1131. final button = find.descendant(
  1132. of: find.byType(CalendarEventEditor),
  1133. matching: find.byWidgetPredicate(
  1134. (widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s,
  1135. ),
  1136. );
  1137. await tapButton(button);
  1138. }
  1139. Future<void> dragDropRescheduleCalendarEvent(DateTime startDate) async {
  1140. final findEventCard = find.byType(EventCard);
  1141. await drag(findEventCard.first, const Offset(0, 300));
  1142. await pumpAndSettle();
  1143. }
  1144. Future<void> openUnscheduledEventsPopup() async {
  1145. final button = find.byType(UnscheduledEventsButton);
  1146. await tapButton(button);
  1147. }
  1148. void findUnscheduledPopup(Matcher matcher, int numUnscheduledEvents) {
  1149. expect(find.byType(UnscheduleEventsList), matcher);
  1150. if (matcher != findsNothing) {
  1151. expect(
  1152. find.byType(UnscheduledEventCell),
  1153. findsNWidgets(numUnscheduledEvents),
  1154. );
  1155. }
  1156. }
  1157. Future<void> clickUnscheduledEvent() async {
  1158. final unscheduledEvent = find.byType(UnscheduledEventCell);
  1159. await tapButton(unscheduledEvent);
  1160. }
  1161. Future<void> tapCreateLinkedDatabaseViewButton(AddButtonAction action) async {
  1162. final findAddButton = find.byType(AddDatabaseViewButton);
  1163. await tapButton(findAddButton);
  1164. final findCreateButton = find.byWidgetPredicate(
  1165. (widget) =>
  1166. widget is TarBarAddButtonActionCell && widget.action == action,
  1167. );
  1168. await tapButton(findCreateButton);
  1169. }
  1170. Future<void> tapTabBarLinkedViewByViewName(String name) async {
  1171. final viewButton = findTabBarLinkViewByViewName(name);
  1172. await tapButton(viewButton);
  1173. }
  1174. Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) {
  1175. return find.byWidgetPredicate(
  1176. (widget) => widget is TabBarItemButton && widget.view.layout == layout,
  1177. );
  1178. }
  1179. Finder findTabBarLinkViewByViewName(String name) {
  1180. return find.byWidgetPredicate(
  1181. (widget) => widget is TabBarItemButton && widget.view.name == name,
  1182. );
  1183. }
  1184. Future<void> renameLinkedView(Finder linkedView, String name) async {
  1185. await tap(linkedView, buttons: kSecondaryButton);
  1186. await pumpAndSettle();
  1187. await tapButton(
  1188. find.byWidgetPredicate(
  1189. (widget) =>
  1190. widget is ActionCellWidget &&
  1191. widget.action == TabBarViewAction.rename,
  1192. ),
  1193. );
  1194. await enterText(
  1195. find.descendant(
  1196. of: find.byType(FlowyFormTextInput),
  1197. matching: find.byType(TextFormField),
  1198. ),
  1199. name,
  1200. );
  1201. final field = find.byWidgetPredicate(
  1202. (widget) =>
  1203. widget is PrimaryTextButton &&
  1204. widget.label == LocaleKeys.button_OK.tr(),
  1205. );
  1206. await tapButton(field);
  1207. }
  1208. Future<void> deleteDatebaseView(Finder linkedView) async {
  1209. await tap(linkedView, buttons: kSecondaryButton);
  1210. await pumpAndSettle();
  1211. await tapButton(
  1212. find.byWidgetPredicate(
  1213. (widget) =>
  1214. widget is ActionCellWidget &&
  1215. widget.action == TabBarViewAction.delete,
  1216. ),
  1217. );
  1218. final okButton = find.byWidgetPredicate(
  1219. (widget) =>
  1220. widget is PrimaryTextButton &&
  1221. widget.label == LocaleKeys.button_OK.tr(),
  1222. );
  1223. await tapButton(okButton);
  1224. }
  1225. Future<void> assertCurrentDatabaseTagIs(DatabaseLayoutPB layout) async {
  1226. switch (layout) {
  1227. case DatabaseLayoutPB.Board:
  1228. expect(find.byType(BoardPage), findsOneWidget);
  1229. break;
  1230. case DatabaseLayoutPB.Calendar:
  1231. expect(find.byType(CalendarPage), findsOneWidget);
  1232. break;
  1233. case DatabaseLayoutPB.Grid:
  1234. expect(find.byType(GridPage), findsOneWidget);
  1235. break;
  1236. default:
  1237. throw Exception('Unknown database layout type: $layout');
  1238. }
  1239. }
  1240. Future<void> selectDatabaseLayoutType(DatabaseLayoutPB layout) async {
  1241. final findLayoutCell = find.byType(DatabaseViewLayoutCell);
  1242. final findText = find.byWidgetPredicate(
  1243. (widget) => widget is FlowyText && widget.text == layout.layoutName(),
  1244. );
  1245. final button = find.descendant(
  1246. of: findLayoutCell,
  1247. matching: findText,
  1248. );
  1249. await tapButton(button);
  1250. }
  1251. Future<void> assertCurrentDatabaseLayoutType(DatabaseLayoutPB layout) async {
  1252. expect(finderForDatabaseLayoutType(layout), findsOneWidget);
  1253. }
  1254. Future<void> tapDatabaseRawDataButton() async {
  1255. await tapButtonWithName(LocaleKeys.importPanel_database.tr());
  1256. }
  1257. Future<void> tapAddSelectOptionButton() async {
  1258. await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr());
  1259. }
  1260. Future<void> tapViewTogglePropertyVisibilityButtonByName(
  1261. String fieldName,
  1262. ) async {
  1263. final field = find.byWidgetPredicate(
  1264. (widget) =>
  1265. widget is GridPropertyCell && widget.fieldInfo.name == fieldName,
  1266. );
  1267. final toggleVisibilityButton =
  1268. find.descendant(of: field, matching: find.byType(FlowyIconButton));
  1269. await tapButton(toggleVisibilityButton);
  1270. }
  1271. }
  1272. Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
  1273. switch (layout) {
  1274. case DatabaseLayoutPB.Board:
  1275. return find.byType(BoardPage);
  1276. case DatabaseLayoutPB.Calendar:
  1277. return find.byType(CalendarPage);
  1278. case DatabaseLayoutPB.Grid:
  1279. return find.byType(GridPage);
  1280. default:
  1281. throw Exception('Unknown database layout type: $layout');
  1282. }
  1283. }
  1284. Finder finderForFieldType(FieldType fieldType) {
  1285. switch (fieldType) {
  1286. case FieldType.Checkbox:
  1287. return find.byType(GridCheckboxCell, skipOffstage: false);
  1288. case FieldType.DateTime:
  1289. return find.byType(GridDateCell, skipOffstage: false);
  1290. case FieldType.LastEditedTime:
  1291. case FieldType.CreatedTime:
  1292. return find.byType(GridTimestampCell, skipOffstage: false);
  1293. case FieldType.SingleSelect:
  1294. return find.byType(GridSingleSelectCell, skipOffstage: false);
  1295. case FieldType.MultiSelect:
  1296. return find.byType(GridMultiSelectCell, skipOffstage: false);
  1297. case FieldType.Checklist:
  1298. return find.byType(GridChecklistCell, skipOffstage: false);
  1299. case FieldType.Number:
  1300. return find.byType(GridNumberCell, skipOffstage: false);
  1301. case FieldType.RichText:
  1302. return find.byType(GridTextCell, skipOffstage: false);
  1303. case FieldType.URL:
  1304. return find.byType(GridURLCell, skipOffstage: false);
  1305. default:
  1306. throw Exception('Unknown field type: $fieldType');
  1307. }
  1308. }