common_operations.dart 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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(
  201. ViewLayoutPB layout, [
  202. String? name,
  203. ]) async {
  204. // create a new page
  205. await tapAddButton();
  206. await tapButtonWithName(layout.menuName);
  207. await pumpAndSettle();
  208. // hover on it and change it's name
  209. if (name != null) {
  210. await hoverOnPageName(
  211. LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
  212. onHover: () async {
  213. await renamePage(name);
  214. await pumpAndSettle();
  215. },
  216. );
  217. await pumpAndSettle();
  218. }
  219. }
  220. Future<void> simulateKeyEvent(
  221. LogicalKeyboardKey key, {
  222. bool isControlPressed = false,
  223. bool isShiftPressed = false,
  224. bool isAltPressed = false,
  225. bool isMetaPressed = false,
  226. }) async {
  227. if (isControlPressed) {
  228. await simulateKeyDownEvent(LogicalKeyboardKey.control);
  229. }
  230. if (isShiftPressed) {
  231. await simulateKeyDownEvent(LogicalKeyboardKey.shift);
  232. }
  233. if (isAltPressed) {
  234. await simulateKeyDownEvent(LogicalKeyboardKey.alt);
  235. }
  236. if (isMetaPressed) {
  237. await simulateKeyDownEvent(LogicalKeyboardKey.meta);
  238. }
  239. await simulateKeyDownEvent(key);
  240. await simulateKeyUpEvent(key);
  241. if (isControlPressed) {
  242. await simulateKeyUpEvent(LogicalKeyboardKey.control);
  243. }
  244. if (isShiftPressed) {
  245. await simulateKeyUpEvent(LogicalKeyboardKey.shift);
  246. }
  247. if (isAltPressed) {
  248. await simulateKeyUpEvent(LogicalKeyboardKey.alt);
  249. }
  250. if (isMetaPressed) {
  251. await simulateKeyUpEvent(LogicalKeyboardKey.meta);
  252. }
  253. await pumpAndSettle();
  254. }
  255. Future<void> openAppInNewTab(String name) async {
  256. await hoverOnPageName(name);
  257. await tap(find.byType(ViewDisclosureButton));
  258. await pumpAndSettle();
  259. await tap(find.text(LocaleKeys.disclosureAction_openNewTab.tr()));
  260. await pumpAndSettle();
  261. }
  262. }
  263. extension ViewLayoutPBTest on ViewLayoutPB {
  264. String get menuName {
  265. switch (this) {
  266. case ViewLayoutPB.Grid:
  267. return LocaleKeys.grid_menuName.tr();
  268. case ViewLayoutPB.Board:
  269. return LocaleKeys.board_menuName.tr();
  270. case ViewLayoutPB.Document:
  271. return LocaleKeys.document_menuName.tr();
  272. case ViewLayoutPB.Calendar:
  273. return LocaleKeys.calendar_menuName.tr();
  274. default:
  275. throw UnsupportedError('Unsupported layout: $this');
  276. }
  277. }
  278. String get referencedMenuName {
  279. switch (this) {
  280. case ViewLayoutPB.Grid:
  281. return LocaleKeys.document_plugins_referencedGrid.tr();
  282. case ViewLayoutPB.Board:
  283. return LocaleKeys.document_plugins_referencedBoard.tr();
  284. case ViewLayoutPB.Calendar:
  285. return LocaleKeys.document_plugins_referencedCalendar.tr();
  286. default:
  287. throw UnsupportedError('Unsupported layout: $this');
  288. }
  289. }
  290. }