database_test_op.dart 40 KB

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