common_operations.dart 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import 'dart:ui';
  2. import 'package:appflowy_backend/log.dart';
  3. import 'package:appflowy/generated/locale_keys.g.dart';
  4. import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
  5. import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
  6. import 'package:appflowy/workspace/presentation/home/menu/app/header/add_button.dart';
  7. import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
  8. import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
  9. import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
  10. import 'package:easy_localization/easy_localization.dart';
  11. import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
  12. import 'package:flutter/material.dart';
  13. import 'package:flutter/services.dart';
  14. import 'package:flutter_test/flutter_test.dart';
  15. import 'util.dart';
  16. extension CommonOperations on WidgetTester {
  17. /// Tap the GetStart button on the launch page.
  18. Future<void> tapGoButton() async {
  19. final goButton = find.byType(GoButton);
  20. await tapButton(goButton);
  21. }
  22. /// Tap the + button on the home page.
  23. Future<void> tapAddButton() async {
  24. final addButton = find.byType(AddButton);
  25. await tapButton(addButton);
  26. }
  27. /// Tap the create document button.
  28. ///
  29. /// Must call [tapAddButton] first.
  30. Future<void> tapCreateDocumentButton() async {
  31. await tapButtonWithName(LocaleKeys.document_menuName.tr());
  32. }
  33. /// Tap the create grid button.
  34. ///
  35. /// Must call [tapAddButton] first.
  36. Future<void> tapCreateGridButton() async {
  37. await tapButtonWithName(LocaleKeys.grid_menuName.tr());
  38. }
  39. /// Tap the create grid button.
  40. ///
  41. /// Must call [tapAddButton] first.
  42. Future<void> tapCreateCalendarButton() async {
  43. await tapButtonWithName(LocaleKeys.calendar_menuName.tr());
  44. }
  45. /// Tap the import button.
  46. ///
  47. /// Must call [tapAddButton] first.
  48. Future<void> tapImportButton() async {
  49. await tapButtonWithName(LocaleKeys.moreAction_import.tr());
  50. }
  51. /// Tap the import from text & markdown button.
  52. ///
  53. /// Must call [tapImportButton] first.
  54. Future<void> tapTextAndMarkdownButton() async {
  55. await tapButtonWithName(LocaleKeys.importPanel_textAndMarkdown.tr());
  56. }
  57. /// Tap the LanguageSelectorOnWelcomePage widget on the launch page.
  58. Future<void> tapLanguageSelectorOnWelcomePage() async {
  59. final languageSelector = find.byType(LanguageSelectorOnWelcomePage);
  60. await tapButton(languageSelector);
  61. }
  62. /// Tap languageItem on LanguageItemsListView.
  63. ///
  64. /// [scrollDelta] is the distance to scroll the ListView.
  65. /// Default value is 100
  66. ///
  67. /// If it is positive -> scroll down.
  68. ///
  69. /// If it is negative -> scroll up.
  70. Future<void> tapLanguageItem({
  71. required String languageCode,
  72. String? countryCode,
  73. double? scrollDelta,
  74. }) async {
  75. final languageItemsListView = find.descendant(
  76. of: find.byType(ListView),
  77. matching: find.byType(Scrollable),
  78. );
  79. final languageItem = find.byWidgetPredicate(
  80. (widget) =>
  81. widget is LanguageItem &&
  82. widget.locale.languageCode == languageCode &&
  83. widget.locale.countryCode == countryCode,
  84. );
  85. // scroll the ListView until zHCNLanguageItem shows on the screen.
  86. await scrollUntilVisible(
  87. languageItem,
  88. scrollDelta ?? 100,
  89. scrollable: languageItemsListView,
  90. // maxHeight of LanguageItemsListView
  91. maxScrolls: 400,
  92. );
  93. try {
  94. await tapButton(languageItem);
  95. } on FlutterError catch (e) {
  96. Log.warn('tapLanguageItem error: $e');
  97. }
  98. }
  99. /// Hover on the widget.
  100. Future<void> hoverOnWidget(
  101. Finder finder, {
  102. Offset? offset,
  103. Future<void> Function()? onHover,
  104. }) async {
  105. try {
  106. final gesture = await createGesture(kind: PointerDeviceKind.mouse);
  107. await gesture.addPointer(location: Offset.zero);
  108. await pump();
  109. await gesture.moveTo(offset ?? getCenter(finder));
  110. await pumpAndSettle();
  111. await onHover?.call();
  112. await gesture.removePointer();
  113. } catch (err) {
  114. Log.error('hoverOnWidget error: $err');
  115. }
  116. }
  117. /// Hover on the page name.
  118. Future<void> hoverOnPageName(
  119. String name, {
  120. Future<void> Function()? onHover,
  121. bool useLast = true,
  122. }) async {
  123. if (useLast) {
  124. await hoverOnWidget(findPageName(name).last, onHover: onHover);
  125. } else {
  126. await hoverOnWidget(findPageName(name).first, onHover: onHover);
  127. }
  128. }
  129. /// open the page with given name.
  130. Future<void> openPage(String name) async {
  131. final finder = findPageName(name);
  132. expect(finder, findsOneWidget);
  133. await tapButton(finder);
  134. }
  135. /// Tap the ... button beside the page name.
  136. ///
  137. /// Must call [hoverOnPageName] first.
  138. Future<void> tapPageOptionButton() async {
  139. final optionButton = find.byType(ViewDisclosureButton);
  140. await tapButton(optionButton);
  141. }
  142. /// Tap the delete page button.
  143. Future<void> tapDeletePageButton() async {
  144. await tapPageOptionButton();
  145. await tapButtonWithName(ViewDisclosureAction.delete.name);
  146. }
  147. /// Tap the rename page button.
  148. Future<void> tapRenamePageButton() async {
  149. await tapPageOptionButton();
  150. await tapButtonWithName(ViewDisclosureAction.rename.name);
  151. }
  152. /// Rename the page.
  153. Future<void> renamePage(String name) async {
  154. await tapRenamePageButton();
  155. await enterText(find.byType(TextFormField), name);
  156. await tapOKButton();
  157. }
  158. Future<void> tapOKButton() async {
  159. final okButton = find.byWidgetPredicate(
  160. (widget) =>
  161. widget is PrimaryTextButton &&
  162. widget.label == LocaleKeys.button_OK.tr(),
  163. );
  164. await tapButton(okButton);
  165. }
  166. /// Tap the restore button.
  167. ///
  168. /// the restore button will show after the current page is deleted.
  169. Future<void> tapRestoreButton() async {
  170. final restoreButton = find.textContaining(
  171. LocaleKeys.deletePagePrompt_restore.tr(),
  172. );
  173. await tapButton(restoreButton);
  174. }
  175. /// Tap the delete permanently button.
  176. ///
  177. /// the restore button will show after the current page is deleted.
  178. Future<void> tapDeletePermanentlyButton() async {
  179. final restoreButton = find.textContaining(
  180. LocaleKeys.deletePagePrompt_deletePermanent.tr(),
  181. );
  182. await tapButton(restoreButton);
  183. }
  184. /// Tap the share button above the document page.
  185. Future<void> tapShareButton() async {
  186. final shareButton = find.byWidgetPredicate(
  187. (widget) => widget is DocumentShareButton,
  188. );
  189. await tapButton(shareButton);
  190. }
  191. /// Tap the export markdown button
  192. ///
  193. /// Must call [tapShareButton] first.
  194. Future<void> tapMarkdownButton() async {
  195. final markdownButton = find.textContaining(
  196. LocaleKeys.shareAction_markdown.tr(),
  197. );
  198. await tapButton(markdownButton);
  199. }
  200. Future<void> createNewPageWithName(ViewLayoutPB layout, String name) async {
  201. // create a new page
  202. await tapAddButton();
  203. await tapButtonWithName(layout.menuName);
  204. await pumpAndSettle();
  205. // hover on it and change it's name
  206. await hoverOnPageName(
  207. LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
  208. onHover: () async {
  209. await renamePage(name);
  210. await pumpAndSettle();
  211. },
  212. );
  213. await pumpAndSettle();
  214. }
  215. Future<void> simulateKeyEvent(
  216. LogicalKeyboardKey key, {
  217. bool isControlPressed = false,
  218. bool isShiftPressed = false,
  219. bool isAltPressed = false,
  220. bool isMetaPressed = false,
  221. }) async {
  222. if (isControlPressed) {
  223. await simulateKeyDownEvent(LogicalKeyboardKey.control);
  224. }
  225. if (isShiftPressed) {
  226. await simulateKeyDownEvent(LogicalKeyboardKey.shift);
  227. }
  228. if (isAltPressed) {
  229. await simulateKeyDownEvent(LogicalKeyboardKey.alt);
  230. }
  231. if (isMetaPressed) {
  232. await simulateKeyDownEvent(LogicalKeyboardKey.meta);
  233. }
  234. await simulateKeyDownEvent(key);
  235. await simulateKeyUpEvent(key);
  236. if (isControlPressed) {
  237. await simulateKeyUpEvent(LogicalKeyboardKey.control);
  238. }
  239. if (isShiftPressed) {
  240. await simulateKeyUpEvent(LogicalKeyboardKey.shift);
  241. }
  242. if (isAltPressed) {
  243. await simulateKeyUpEvent(LogicalKeyboardKey.alt);
  244. }
  245. if (isMetaPressed) {
  246. await simulateKeyUpEvent(LogicalKeyboardKey.meta);
  247. }
  248. await pumpAndSettle();
  249. }
  250. Future<void> openAppInNewTab(String name) async {
  251. await hoverOnPageName(name);
  252. await tap(find.byType(ViewDisclosureButton));
  253. await pumpAndSettle();
  254. await tap(find.text(LocaleKeys.disclosureAction_openNewTab.tr()));
  255. await pumpAndSettle();
  256. }
  257. }
  258. extension ViewLayoutPBTest on ViewLayoutPB {
  259. String get menuName {
  260. switch (this) {
  261. case ViewLayoutPB.Grid:
  262. return LocaleKeys.grid_menuName.tr();
  263. case ViewLayoutPB.Board:
  264. return LocaleKeys.board_menuName.tr();
  265. case ViewLayoutPB.Document:
  266. return LocaleKeys.document_menuName.tr();
  267. case ViewLayoutPB.Calendar:
  268. return LocaleKeys.calendar_menuName.tr();
  269. default:
  270. throw UnsupportedError('Unsupported layout: $this');
  271. }
  272. }
  273. String get referencedMenuName {
  274. switch (this) {
  275. case ViewLayoutPB.Grid:
  276. return LocaleKeys.document_plugins_referencedGrid.tr();
  277. case ViewLayoutPB.Board:
  278. return LocaleKeys.document_plugins_referencedBoard.tr();
  279. case ViewLayoutPB.Calendar:
  280. return LocaleKeys.document_plugins_referencedCalendar.tr();
  281. default:
  282. throw UnsupportedError('Unsupported layout: $this');
  283. }
  284. }
  285. }