database_test_op.dart 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529
  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.byWidgetPredicate(
  776. (widget) => widget is FlowyText && widget.text == rowCountString(num),
  777. );
  778. expect(text, findsOneWidget);
  779. }
  780. Future<void> createField(FieldType fieldType, String name) async {
  781. await scrollToRight(find.byType(GridPage));
  782. await tapNewPropertyButton();
  783. await renameField(name);
  784. await tapTypeOptionButton();
  785. await selectFieldType(fieldType);
  786. await dismissFieldEditor();
  787. }
  788. Future<void> tapDatabaseSettingButton() async {
  789. await tapButton(find.byType(SettingButton));
  790. }
  791. Future<void> tapDatabaseFilterButton() async {
  792. await tapButton(find.byType(FilterButton));
  793. }
  794. Future<void> tapDatabaseSortButton() async {
  795. await tapButton(find.byType(SortButton));
  796. }
  797. Future<void> tapCreateFilterByFieldType(
  798. FieldType fieldType,
  799. String title,
  800. ) async {
  801. final findFilter = find.byWidgetPredicate(
  802. (widget) =>
  803. widget is GridFilterPropertyCell &&
  804. widget.fieldInfo.fieldType == fieldType &&
  805. widget.fieldInfo.name == title,
  806. );
  807. await tapButton(findFilter);
  808. }
  809. Future<void> tapFilterButtonInGrid(String filterName) async {
  810. final findFilter = find.byType(FilterMenuItem);
  811. final button = find.descendant(
  812. of: findFilter,
  813. matching: find.text(filterName),
  814. );
  815. await tapButton(button);
  816. }
  817. Future<void> tapCreateSortByFieldType(
  818. FieldType fieldType,
  819. String title,
  820. ) async {
  821. final findSort = find.byWidgetPredicate(
  822. (widget) =>
  823. widget is GridSortPropertyCell &&
  824. widget.fieldInfo.fieldType == fieldType &&
  825. widget.fieldInfo.name == title,
  826. );
  827. await tapButton(findSort);
  828. }
  829. // Must call [tapSortMenuInSettingBar] first.
  830. Future<void> tapCreateSortByFieldTypeInSortMenu(
  831. FieldType fieldType,
  832. String title,
  833. ) async {
  834. await tapButton(find.byType(DatabaseAddSortButton));
  835. final findSort = find.byWidgetPredicate(
  836. (widget) =>
  837. widget is GridSortPropertyCell &&
  838. widget.fieldInfo.fieldType == fieldType &&
  839. widget.fieldInfo.name == title,
  840. );
  841. await tapButton(findSort);
  842. await pumpAndSettle();
  843. }
  844. Future<void> tapSortMenuInSettingBar() async {
  845. await tapButton(find.byType(SortMenu));
  846. await pumpAndSettle();
  847. }
  848. /// Must call [tapSortMenuInSettingBar] first.
  849. Future<void> tapSortButtonByName(String name) async {
  850. final findSortItem = find.byWidgetPredicate(
  851. (widget) =>
  852. widget is DatabaseSortItem && widget.sortInfo.fieldInfo.name == name,
  853. );
  854. await tapButton(findSortItem);
  855. }
  856. /// Must call [tapSortButtonByName] first.
  857. Future<void> tapSortByDescending() async {
  858. await tapButton(
  859. find.descendant(
  860. of: find.byType(OrderPannelItem),
  861. matching: find.byWidgetPredicate(
  862. (widget) =>
  863. widget is FlowyText &&
  864. widget.text == LocaleKeys.grid_sort_descending.tr(),
  865. ),
  866. ),
  867. );
  868. await sendKeyEvent(LogicalKeyboardKey.escape);
  869. await pumpAndSettle();
  870. }
  871. /// Must call [tapSortMenuInSettingBar] first.
  872. Future<void> tapAllSortButton() async {
  873. await tapButton(find.byType(DatabaseDeleteSortButton));
  874. }
  875. Future<void> scrollOptionFilterListByOffset(Offset offset) async {
  876. await drag(find.byType(SelectOptionFilterEditor), offset);
  877. await pumpAndSettle();
  878. }
  879. Future<void> enterTextInTextFilter(String text) async {
  880. final findEditor = find.byType(TextFilterEditor);
  881. final findTextField = find.descendant(
  882. of: findEditor,
  883. matching: find.byType(FlowyTextField),
  884. );
  885. await enterText(findTextField, text);
  886. await pumpAndSettle(const Duration(milliseconds: 300));
  887. }
  888. Future<void> tapDisclosureButtonInFinder(Finder finder) async {
  889. final findDisclosure = find.descendant(
  890. of: finder,
  891. matching: find.byType(DisclosureButton),
  892. );
  893. await tapButton(findDisclosure);
  894. }
  895. /// must call [tapDisclosureButtonInFinder] first.
  896. Future<void> tapDeleteFilterButtonInGrid() async {
  897. await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr()));
  898. }
  899. Future<void> tapCheckboxFilterButtonInGrid() async {
  900. await tapButton(find.byType(CheckboxFilterConditionList));
  901. }
  902. Future<void> tapChecklistFilterButtonInGrid() async {
  903. await tapButton(find.byType(ChecklistFilterConditionList));
  904. }
  905. /// The [SelectOptionFilterList] must show up first.
  906. Future<void> tapOptionFilterWithName(String name) async {
  907. final findCell = find.descendant(
  908. of: find.byType(SelectOptionFilterList),
  909. matching: find.byWidgetPredicate(
  910. (widget) =>
  911. widget is SelectOptionFilterCell && widget.option.name == name,
  912. skipOffstage: false,
  913. ),
  914. skipOffstage: false,
  915. );
  916. expect(findCell, findsOneWidget);
  917. await tapButton(findCell, warnIfMissed: false);
  918. }
  919. Future<void> tapCheckedButtonOnCheckboxFilter() async {
  920. final button = find.descendant(
  921. of: find.byType(HoverButton),
  922. matching: find.text(LocaleKeys.grid_checkboxFilter_isChecked.tr()),
  923. );
  924. await tapButton(button);
  925. }
  926. Future<void> tapUnCheckedButtonOnCheckboxFilter() async {
  927. final button = find.descendant(
  928. of: find.byType(HoverButton),
  929. matching: find.text(LocaleKeys.grid_checkboxFilter_isUnchecked.tr()),
  930. );
  931. await tapButton(button);
  932. }
  933. Future<void> tapCompletedButtonOnChecklistFilter() async {
  934. final button = find.descendant(
  935. of: find.byType(HoverButton),
  936. matching: find.text(LocaleKeys.grid_checklistFilter_isComplete.tr()),
  937. );
  938. await tapButton(button);
  939. }
  940. Future<void> tapUnCompletedButtonOnChecklistFilter() async {
  941. final button = find.descendant(
  942. of: find.byType(HoverButton),
  943. matching: find.text(LocaleKeys.grid_checklistFilter_isIncomplted.tr()),
  944. );
  945. await tapButton(button);
  946. }
  947. /// Should call [tapDatabaseSettingButton] first.
  948. Future<void> tapViewPropertiesButton() async {
  949. final findSettingItem = find.byType(DatabaseSettingItem);
  950. final findLayoutButton = find.byWidgetPredicate(
  951. (widget) =>
  952. widget is FlowyText &&
  953. widget.text == DatabaseSettingAction.showProperties.title(),
  954. );
  955. final button = find.descendant(
  956. of: findSettingItem,
  957. matching: findLayoutButton,
  958. );
  959. await tapButton(button);
  960. }
  961. /// Should call [tapDatabaseSettingButton] first.
  962. Future<void> tapDatabaseLayoutButton() async {
  963. final findSettingItem = find.byType(DatabaseSettingItem);
  964. final findLayoutButton = find.byWidgetPredicate(
  965. (widget) =>
  966. widget is FlowyText &&
  967. widget.text == DatabaseSettingAction.showLayout.title(),
  968. );
  969. final button = find.descendant(
  970. of: findSettingItem,
  971. matching: findLayoutButton,
  972. );
  973. await tapButton(button);
  974. }
  975. Future<void> tapCalendarLayoutSettingButton() async {
  976. final findSettingItem = find.byType(DatabaseSettingItem);
  977. final findLayoutButton = find.byWidgetPredicate(
  978. (widget) =>
  979. widget is FlowyText &&
  980. widget.text == DatabaseSettingAction.showCalendarLayout.title(),
  981. );
  982. final button = find.descendant(
  983. of: findSettingItem,
  984. matching: findLayoutButton,
  985. );
  986. await tapButton(button);
  987. }
  988. Future<void> tapFirstDayOfWeek() async {
  989. await tapButton(find.byType(FirstDayOfWeek));
  990. }
  991. Future<void> tapFirstDayOfWeekStartFromSunday() async {
  992. final finder = find.byWidgetPredicate(
  993. (widget) => widget is StartFromButton && widget.dayIndex == 0,
  994. );
  995. await tapButton(finder);
  996. }
  997. Future<void> tapFirstDayOfWeekStartFromMonday() async {
  998. final finder = find.byWidgetPredicate(
  999. (widget) => widget is StartFromButton && widget.dayIndex == 1,
  1000. );
  1001. await tapButton(finder);
  1002. // Dismiss the popover overlay in cause of obscure the tapButton
  1003. // in the next test case.
  1004. await sendKeyEvent(LogicalKeyboardKey.escape);
  1005. await pumpAndSettle(const Duration(milliseconds: 200));
  1006. }
  1007. void assertFirstDayOfWeekStartFromMonday() {
  1008. final finder = find.byWidgetPredicate(
  1009. (widget) =>
  1010. widget is StartFromButton &&
  1011. widget.dayIndex == 1 &&
  1012. widget.isSelected == true,
  1013. );
  1014. expect(finder, findsOneWidget);
  1015. }
  1016. void assertFirstDayOfWeekStartFromSunday() {
  1017. final finder = find.byWidgetPredicate(
  1018. (widget) =>
  1019. widget is StartFromButton &&
  1020. widget.dayIndex == 0 &&
  1021. widget.isSelected == true,
  1022. );
  1023. expect(finder, findsOneWidget);
  1024. }
  1025. Future<void> scrollToToday() async {
  1026. final todayCell = find.byWidgetPredicate(
  1027. (widget) => widget is CalendarDayCard && widget.isToday,
  1028. );
  1029. final scrollable = find
  1030. .descendant(
  1031. of: find.byType(MonthView<CalendarDayEvent>),
  1032. matching: find.byWidgetPredicate(
  1033. (widget) => widget is Scrollable && widget.axis == Axis.vertical,
  1034. ),
  1035. )
  1036. .first;
  1037. await scrollUntilVisible(
  1038. todayCell,
  1039. 300,
  1040. scrollable: scrollable,
  1041. );
  1042. await pumpAndSettle(const Duration(milliseconds: 300));
  1043. }
  1044. Future<void> hoverOnTodayCalendarCell() async {
  1045. final todayCell = find.byWidgetPredicate(
  1046. (widget) => widget is CalendarDayCard && widget.isToday,
  1047. );
  1048. await hoverOnWidget(todayCell);
  1049. }
  1050. Future<void> tapAddCalendarEventButton() async {
  1051. final findFlowyButton = find.byType(FlowyIconButton);
  1052. final findNewEventButton = find.byType(NewEventButton);
  1053. final button = find.descendant(
  1054. of: findNewEventButton,
  1055. matching: findFlowyButton,
  1056. );
  1057. await tapButton(button);
  1058. }
  1059. /// Checks for a certain number of events. Parameters [date] and [title] can
  1060. /// also be provided to restrict the scope of the search
  1061. void assertNumberOfEventsInCalendar(int number, {String? title}) {
  1062. Finder findEvents = find.byType(EventCard);
  1063. if (title != null) {
  1064. findEvents = find.descendant(of: findEvents, matching: find.text(title));
  1065. }
  1066. expect(findEvents, findsNWidgets(number));
  1067. }
  1068. void assertNumberOfEventsOnSpecificDay(
  1069. int number,
  1070. DateTime date, {
  1071. String? title,
  1072. }) {
  1073. final findDayCell = find.byWidgetPredicate(
  1074. (widget) =>
  1075. widget is CalendarDayCard &&
  1076. isSameDay(
  1077. widget.date,
  1078. date,
  1079. ),
  1080. );
  1081. Finder findEvents = find.descendant(
  1082. of: findDayCell,
  1083. matching: find.byType(EventCard),
  1084. );
  1085. if (title != null) {
  1086. findEvents = find.descendant(of: findEvents, matching: find.text(title));
  1087. }
  1088. expect(findEvents, findsNWidgets(number));
  1089. }
  1090. Future<void> doubleClickCalendarCell(DateTime date) async {
  1091. final todayCell = find.byWidgetPredicate(
  1092. (widget) => widget is CalendarDayCard && isSameDay(date, widget.date),
  1093. );
  1094. final location = getTopLeft(todayCell).translate(10, 10);
  1095. await doubleTapAt(location);
  1096. }
  1097. Future<void> openCalendarEvent({required index, DateTime? date}) async {
  1098. final findDayCell = find.byWidgetPredicate(
  1099. (widget) =>
  1100. widget is CalendarDayCard &&
  1101. isSameDay(widget.date, date ?? DateTime.now()),
  1102. );
  1103. final cards = find.descendant(
  1104. of: findDayCell,
  1105. matching: find.byType(EventCard),
  1106. );
  1107. await tapButton(cards.at(index));
  1108. }
  1109. void assertEventEditorOpen() {
  1110. expect(find.byType(CalendarEventEditor), findsOneWidget);
  1111. }
  1112. Future<void> dismissEventEditor() async {
  1113. await simulateKeyEvent(LogicalKeyboardKey.escape);
  1114. }
  1115. Future<void> editEventTitle(String title) async {
  1116. final textField = find.descendant(
  1117. of: find.byType(CalendarEventEditor),
  1118. matching: find.byType(FlowyTextField),
  1119. );
  1120. await enterText(textField, title);
  1121. await pumpAndSettle(const Duration(milliseconds: 300));
  1122. }
  1123. Future<void> openEventToRowDetailPage() async {
  1124. final button = find.descendant(
  1125. of: find.byType(CalendarEventEditor),
  1126. matching: find.byWidgetPredicate(
  1127. (widget) => widget is FlowySvg && widget.svg == FlowySvgs.full_view_s,
  1128. ),
  1129. );
  1130. await tapButton(button);
  1131. }
  1132. Future<void> deleteEventFromEventEditor() async {
  1133. final button = find.descendant(
  1134. of: find.byType(CalendarEventEditor),
  1135. matching: find.byWidgetPredicate(
  1136. (widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s,
  1137. ),
  1138. );
  1139. await tapButton(button);
  1140. }
  1141. Future<void> dragDropRescheduleCalendarEvent(DateTime startDate) async {
  1142. final findEventCard = find.byType(EventCard);
  1143. await drag(findEventCard.first, const Offset(0, 300));
  1144. await pumpAndSettle();
  1145. }
  1146. Future<void> openUnscheduledEventsPopup() async {
  1147. final button = find.byType(UnscheduledEventsButton);
  1148. await tapButton(button);
  1149. }
  1150. void findUnscheduledPopup(Matcher matcher, int numUnscheduledEvents) {
  1151. expect(find.byType(UnscheduleEventsList), matcher);
  1152. if (matcher != findsNothing) {
  1153. expect(
  1154. find.byType(UnscheduledEventCell),
  1155. findsNWidgets(numUnscheduledEvents),
  1156. );
  1157. }
  1158. }
  1159. Future<void> clickUnscheduledEvent() async {
  1160. final unscheduledEvent = find.byType(UnscheduledEventCell);
  1161. await tapButton(unscheduledEvent);
  1162. }
  1163. Future<void> tapCreateLinkedDatabaseViewButton(AddButtonAction action) async {
  1164. final findAddButton = find.byType(AddDatabaseViewButton);
  1165. await tapButton(findAddButton);
  1166. final findCreateButton = find.byWidgetPredicate(
  1167. (widget) =>
  1168. widget is TarBarAddButtonActionCell && widget.action == action,
  1169. );
  1170. await tapButton(findCreateButton);
  1171. }
  1172. Future<void> tapTabBarLinkedViewByViewName(String name) async {
  1173. final viewButton = findTabBarLinkViewByViewName(name);
  1174. await tapButton(viewButton);
  1175. }
  1176. Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) {
  1177. return find.byWidgetPredicate(
  1178. (widget) => widget is TabBarItemButton && widget.view.layout == layout,
  1179. );
  1180. }
  1181. Finder findTabBarLinkViewByViewName(String name) {
  1182. return find.byWidgetPredicate(
  1183. (widget) => widget is TabBarItemButton && widget.view.name == name,
  1184. );
  1185. }
  1186. Future<void> renameLinkedView(Finder linkedView, String name) async {
  1187. await tap(linkedView, buttons: kSecondaryButton);
  1188. await pumpAndSettle();
  1189. await tapButton(
  1190. find.byWidgetPredicate(
  1191. (widget) =>
  1192. widget is ActionCellWidget &&
  1193. widget.action == TabBarViewAction.rename,
  1194. ),
  1195. );
  1196. await enterText(
  1197. find.descendant(
  1198. of: find.byType(FlowyFormTextInput),
  1199. matching: find.byType(TextFormField),
  1200. ),
  1201. name,
  1202. );
  1203. final field = find.byWidgetPredicate(
  1204. (widget) =>
  1205. widget is PrimaryTextButton &&
  1206. widget.label == LocaleKeys.button_OK.tr(),
  1207. );
  1208. await tapButton(field);
  1209. }
  1210. Future<void> deleteDatebaseView(Finder linkedView) async {
  1211. await tap(linkedView, buttons: kSecondaryButton);
  1212. await pumpAndSettle();
  1213. await tapButton(
  1214. find.byWidgetPredicate(
  1215. (widget) =>
  1216. widget is ActionCellWidget &&
  1217. widget.action == TabBarViewAction.delete,
  1218. ),
  1219. );
  1220. final okButton = find.byWidgetPredicate(
  1221. (widget) =>
  1222. widget is PrimaryTextButton &&
  1223. widget.label == LocaleKeys.button_OK.tr(),
  1224. );
  1225. await tapButton(okButton);
  1226. }
  1227. Future<void> assertCurrentDatabaseTagIs(DatabaseLayoutPB layout) async {
  1228. switch (layout) {
  1229. case DatabaseLayoutPB.Board:
  1230. expect(find.byType(BoardPage), findsOneWidget);
  1231. break;
  1232. case DatabaseLayoutPB.Calendar:
  1233. expect(find.byType(CalendarPage), findsOneWidget);
  1234. break;
  1235. case DatabaseLayoutPB.Grid:
  1236. expect(find.byType(GridPage), findsOneWidget);
  1237. break;
  1238. default:
  1239. throw Exception('Unknown database layout type: $layout');
  1240. }
  1241. }
  1242. Future<void> selectDatabaseLayoutType(DatabaseLayoutPB layout) async {
  1243. final findLayoutCell = find.byType(DatabaseViewLayoutCell);
  1244. final findText = find.byWidgetPredicate(
  1245. (widget) => widget is FlowyText && widget.text == layout.layoutName(),
  1246. );
  1247. final button = find.descendant(
  1248. of: findLayoutCell,
  1249. matching: findText,
  1250. );
  1251. await tapButton(button);
  1252. }
  1253. Future<void> assertCurrentDatabaseLayoutType(DatabaseLayoutPB layout) async {
  1254. expect(finderForDatabaseLayoutType(layout), findsOneWidget);
  1255. }
  1256. Future<void> tapDatabaseRawDataButton() async {
  1257. await tapButtonWithName(LocaleKeys.importPanel_database.tr());
  1258. }
  1259. Future<void> tapAddSelectOptionButton() async {
  1260. await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr());
  1261. }
  1262. Future<void> tapViewTogglePropertyVisibilityButtonByName(
  1263. String fieldName,
  1264. ) async {
  1265. final field = find.byWidgetPredicate(
  1266. (widget) =>
  1267. widget is GridPropertyCell && widget.fieldInfo.name == fieldName,
  1268. );
  1269. final toggleVisibilityButton =
  1270. find.descendant(of: field, matching: find.byType(FlowyIconButton));
  1271. await tapButton(toggleVisibilityButton);
  1272. }
  1273. }
  1274. Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
  1275. switch (layout) {
  1276. case DatabaseLayoutPB.Board:
  1277. return find.byType(BoardPage);
  1278. case DatabaseLayoutPB.Calendar:
  1279. return find.byType(CalendarPage);
  1280. case DatabaseLayoutPB.Grid:
  1281. return find.byType(GridPage);
  1282. default:
  1283. throw Exception('Unknown database layout type: $layout');
  1284. }
  1285. }
  1286. Finder finderForFieldType(FieldType fieldType) {
  1287. switch (fieldType) {
  1288. case FieldType.Checkbox:
  1289. return find.byType(GridCheckboxCell, skipOffstage: false);
  1290. case FieldType.DateTime:
  1291. return find.byType(GridDateCell, skipOffstage: false);
  1292. case FieldType.LastEditedTime:
  1293. case FieldType.CreatedTime:
  1294. return find.byType(GridTimestampCell, skipOffstage: false);
  1295. case FieldType.SingleSelect:
  1296. return find.byType(GridSingleSelectCell, skipOffstage: false);
  1297. case FieldType.MultiSelect:
  1298. return find.byType(GridMultiSelectCell, skipOffstage: false);
  1299. case FieldType.Checklist:
  1300. return find.byType(GridChecklistCell, skipOffstage: false);
  1301. case FieldType.Number:
  1302. return find.byType(GridNumberCell, skipOffstage: false);
  1303. case FieldType.RichText:
  1304. return find.byType(GridTextCell, skipOffstage: false);
  1305. case FieldType.URL:
  1306. return find.byType(GridURLCell, skipOffstage: false);
  1307. default:
  1308. throw Exception('Unknown field type: $fieldType');
  1309. }
  1310. }