소스 검색

chore: add database sort integration test (#2866)

* chore: add sort test

* chore: add tests

* chore: update tests

* fix: switch folder test

---------

Co-authored-by: Lucas.Xu <[email protected]>
Nathan.fooo 1 년 전
부모
커밋
5595649f53

+ 283 - 0
frontend/appflowy_flutter/integration_test/database_sort_test.dart

@@ -0,0 +1,283 @@
+import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'util/database_test_op.dart';
+import 'util/util.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  group('grid', () {
+    const location = 'import_files';
+
+    setUp(() async {
+      await TestFolder.cleanTestLocation(location);
+      await TestFolder.setTestLocation(location);
+    });
+
+    tearDown(() async {
+      await TestFolder.cleanTestLocation(location);
+    });
+
+    tearDownAll(() async {
+      await TestFolder.cleanTestLocation(null);
+    });
+
+    testWidgets('add text sort', (tester) async {
+      await tester.openV020database();
+      // create a filter
+      await tester.tapDatabaseSortButton();
+      await tester.tapCreateSortByFieldType(FieldType.RichText, 'Name');
+
+      // check the text cell order
+      final textCells = <String>[
+        '',
+        '',
+        '',
+        '',
+        '',
+        'A',
+        'B',
+        'C',
+        'D',
+        'E',
+      ];
+      for (final (index, content) in textCells.indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.RichText,
+          content: content,
+        );
+      }
+
+      // open the sort menu and select order by descending
+      await tester.tapSortMenuInSettingBar();
+      await tester.tapSortButtonByName('Name');
+      await tester.tapSortByDescending();
+      for (final (index, content) in <String>[
+        'E',
+        'D',
+        'C',
+        'B',
+        'A',
+        '',
+        '',
+        '',
+        '',
+        '',
+      ].indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.RichText,
+          content: content,
+        );
+      }
+
+      // delete all sorts
+      await tester.tapSortMenuInSettingBar();
+      await tester.tapAllSortButton();
+
+      // check the text cell order
+      for (final (index, content) in <String>[
+        'A',
+        'B',
+        'C',
+        'D',
+        'E',
+        '',
+        '',
+        '',
+        '',
+        '',
+      ].indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.RichText,
+          content: content,
+        );
+      }
+      await tester.pumpAndSettle();
+    });
+
+    testWidgets('add checkbox sort', (tester) async {
+      await tester.openV020database();
+      // create a filter
+      await tester.tapDatabaseSortButton();
+      await tester.tapCreateSortByFieldType(FieldType.Checkbox, 'Done');
+
+      // check the checkbox cell order
+      for (final (index, content) in <bool>[
+        false,
+        false,
+        false,
+        false,
+        false,
+        true,
+        true,
+        true,
+        true,
+        true,
+      ].indexed) {
+        await tester.assertCheckboxCell(
+          rowIndex: index,
+          isSelected: content,
+        );
+      }
+
+      // open the sort menu and select order by descending
+      await tester.tapSortMenuInSettingBar();
+      await tester.tapSortButtonByName('Done');
+      await tester.tapSortByDescending();
+      for (final (index, content) in <bool>[
+        true,
+        true,
+        true,
+        true,
+        true,
+        false,
+        false,
+        false,
+        false,
+        false,
+      ].indexed) {
+        await tester.assertCheckboxCell(
+          rowIndex: index,
+          isSelected: content,
+        );
+      }
+
+      await tester.pumpAndSettle();
+    });
+
+    testWidgets('add number sort', (tester) async {
+      await tester.openV020database();
+      // create a filter
+      await tester.tapDatabaseSortButton();
+      await tester.tapCreateSortByFieldType(FieldType.Number, 'number');
+
+      // check the number cell order
+      for (final (index, content) in <String>[
+        '',
+        '-2',
+        '-1',
+        '0.1',
+        '0.2',
+        '1',
+        '2',
+        '10',
+        '11',
+        '12',
+      ].indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.Number,
+          content: content,
+        );
+      }
+
+      // open the sort menu and select order by descending
+      await tester.tapSortMenuInSettingBar();
+      await tester.tapSortButtonByName('number');
+      await tester.tapSortByDescending();
+      for (final (index, content) in <String>[
+        '12',
+        '11',
+        '10',
+        '2',
+        '1',
+        '0.2',
+        '0.1',
+        '-1',
+        '-2',
+        '',
+      ].indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.Number,
+          content: content,
+        );
+      }
+
+      await tester.pumpAndSettle();
+    });
+
+    testWidgets('add number and text sort', (tester) async {
+      await tester.openV020database();
+      // create a filter
+      await tester.tapDatabaseSortButton();
+      await tester.tapCreateSortByFieldType(FieldType.Number, 'number');
+
+      // open the sort menu and select number order by descending
+      await tester.tapSortMenuInSettingBar();
+      await tester.tapSortButtonByName('number');
+      await tester.tapSortByDescending();
+      for (final (index, content) in <String>[
+        '12',
+        '11',
+        '10',
+        '2',
+        '1',
+        '0.2',
+        '0.1',
+        '-1',
+        '-2',
+        '',
+      ].indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.Number,
+          content: content,
+        );
+      }
+
+      await tester.tapSortMenuInSettingBar();
+      await tester.tapCreateSortByFieldTypeInSortMenu(
+        FieldType.RichText,
+        'Name',
+      );
+
+      // check number cell order
+      for (final (index, content) in <String>[
+        '12',
+        '11',
+        '10',
+        '2',
+        '',
+        '-1',
+        '-2',
+        '0.1',
+        '0.2',
+        '1',
+      ].indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.Number,
+          content: content,
+        );
+      }
+
+      // check text cell order
+      for (final (index, content) in <String>[
+        '',
+        '',
+        '',
+        '',
+        '',
+        'A',
+        'B',
+        'C',
+        'D',
+        'E',
+      ].indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.RichText,
+          content: content,
+        );
+      }
+
+      await tester.pumpAndSettle();
+    });
+  });
+}

+ 2 - 0
frontend/appflowy_flutter/integration_test/runner.dart

@@ -15,6 +15,7 @@ import 'database_setting_test.dart' as database_setting_test;
 import 'database_filter_test.dart' as database_filter_test;
 import 'database_view_test.dart' as database_view_test;
 import 'database_calendar_test.dart' as database_calendar_test;
+import 'database_sort_test.dart' as database_sort_test;
 
 /// The main task runner for all integration tests in AppFlowy.
 ///
@@ -40,6 +41,7 @@ void main() {
   database_row_test.main();
   database_setting_test.main();
   database_filter_test.main();
+  database_sort_test.main();
   database_view_test.main();
   database_calendar_test.main();
 

+ 6 - 3
frontend/appflowy_flutter/integration_test/switch_folder_test.dart

@@ -1,9 +1,12 @@
 import 'package:appflowy/workspace/application/settings/prelude.dart';
+import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
+import 'package:flowy_infra/uuid.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:integration_test/integration_test.dart';
 
 import 'util/mock/mock_file_picker.dart';
 import 'util/util.dart';
+import 'package:path/path.dart' as p;
 
 void main() {
   IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -25,12 +28,12 @@ void main() {
     });
 
     testWidgets('switch to B from A, then switch to A again', (tester) async {
-      const String userA = 'userA';
-      const String userB = 'userB';
+      final userA = uuid();
+      final userB = uuid();
 
       await TestFolder.cleanTestLocation(userA);
       await TestFolder.cleanTestLocation(userB);
-      await TestFolder.setTestLocation(userA);
+      await TestFolder.setTestLocation(p.join(userA, appFlowyDataFolder));
 
       await tester.initializeAppFlowy();
 

+ 77 - 3
frontend/appflowy_flutter/integration_test/util/database_test_op.dart

@@ -16,8 +16,13 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/
 import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/order_panel.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_menu.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart';
 import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
 import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
 import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
@@ -33,8 +38,7 @@ import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra_ui/style_widget/icon_button.dart';
-import 'package:flowy_infra_ui/style_widget/text_field.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/text_input.dart';
 import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
 import 'package:flutter/gestures.dart';
@@ -52,7 +56,6 @@ import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
 import 'package:table_calendar/table_calendar.dart';
 
@@ -603,6 +606,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
     await tapButton(find.byType(FilterButton));
   }
 
+  Future<void> tapDatabaseSortButton() async {
+    await tapButton(find.byType(SortButton));
+  }
+
   Future<void> tapCreateFilterByFieldType(
     FieldType fieldType,
     String title,
@@ -627,6 +634,73 @@ extension AppFlowyDatabaseTest on WidgetTester {
     await tapButton(button);
   }
 
+  Future<void> tapCreateSortByFieldType(
+    FieldType fieldType,
+    String title,
+  ) async {
+    final findSort = find.byWidgetPredicate(
+      (widget) =>
+          widget is GridSortPropertyCell &&
+          widget.fieldInfo.fieldType == fieldType &&
+          widget.fieldInfo.name == title,
+    );
+
+    await tapButton(findSort);
+  }
+
+  // Must call [tapSortMenuInSettingBar] first.
+  Future<void> tapCreateSortByFieldTypeInSortMenu(
+    FieldType fieldType,
+    String title,
+  ) async {
+    await tapButton(find.byType(DatabaseAddSortButton));
+
+    final findSort = find.byWidgetPredicate(
+      (widget) =>
+          widget is GridSortPropertyCell &&
+          widget.fieldInfo.fieldType == fieldType &&
+          widget.fieldInfo.name == title,
+    );
+
+    await tapButton(findSort);
+    await pumpAndSettle();
+  }
+
+  Future<void> tapSortMenuInSettingBar() async {
+    await tapButton(find.byType(SortMenu));
+    await pumpAndSettle();
+  }
+
+  /// Must call [tapSortMenuInSettingBar] first.
+  Future<void> tapSortButtonByName(String name) async {
+    final findSortItem = find.byWidgetPredicate(
+      (widget) =>
+          widget is DatabaseSortItem && widget.sortInfo.fieldInfo.name == name,
+    );
+    await tapButton(findSortItem);
+  }
+
+  /// Must call [tapSortButtonByName] first.
+  Future<void> tapSortByDescending() async {
+    await tapButton(
+      find.descendant(
+        of: find.byType(OrderPannelItem),
+        matching: find.byWidgetPredicate(
+          (widget) =>
+              widget is FlowyText &&
+              widget.text == LocaleKeys.grid_sort_descending.tr(),
+        ),
+      ),
+    );
+    await sendKeyEvent(LogicalKeyboardKey.escape);
+    await pumpAndSettle();
+  }
+
+  /// Must call [tapSortMenuInSettingBar] first.
+  Future<void> tapAllSortButton() async {
+    await tapButton(find.byType(DatabaseDeleteSortButton));
+  }
+
   Future<void> scrollOptionFilterListByOffset(Offset offset) async {
     await drag(find.byType(SelectOptionFilterEditor), offset);
     await pumpAndSettle();

+ 6 - 6
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart

@@ -59,7 +59,7 @@ class _GridCreateSortListState extends State<GridCreateSortList> {
             final cells = state.creatableFields.map((fieldInfo) {
               return SizedBox(
                 height: GridSize.popoverItemHeight,
-                child: _SortPropertyCell(
+                child: GridSortPropertyCell(
                   fieldInfo: fieldInfo,
                   onTap: (fieldInfo) => createSort(fieldInfo),
                 ),
@@ -69,7 +69,7 @@ class _GridCreateSortListState extends State<GridCreateSortList> {
             final List<Widget> slivers = [
               SliverPersistentHeader(
                 pinned: true,
-                delegate: _FilterTextFieldDelegate(),
+                delegate: _SortTextFieldDelegate(),
               ),
               SliverToBoxAdapter(
                 child: ListView.separated(
@@ -109,8 +109,8 @@ class _GridCreateSortListState extends State<GridCreateSortList> {
   }
 }
 
-class _FilterTextFieldDelegate extends SliverPersistentHeaderDelegate {
-  _FilterTextFieldDelegate();
+class _SortTextFieldDelegate extends SliverPersistentHeaderDelegate {
+  _SortTextFieldDelegate();
 
   double fixHeight = 46;
 
@@ -146,10 +146,10 @@ class _FilterTextFieldDelegate extends SliverPersistentHeaderDelegate {
   }
 }
 
-class _SortPropertyCell extends StatelessWidget {
+class GridSortPropertyCell extends StatelessWidget {
   final FieldInfo fieldInfo;
   final Function(FieldInfo) onTap;
-  const _SortPropertyCell({
+  const GridSortPropertyCell({
     required this.fieldInfo,
     required this.onTap,
     Key? key,

+ 23 - 16
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/order_panel.dart

@@ -1,7 +1,6 @@
-import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pbenum.dart';
-import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
@@ -13,12 +12,9 @@ class OrderPanel extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final List<Widget> children = SortConditionPB.values.map((condition) {
-      return SizedBox(
-        height: GridSize.popoverItemHeight,
-        child: FlowyButton(
-          text: FlowyText.medium(textFromCondition(condition)),
-          onTap: () => onCondition(condition),
-        ),
+      return OrderPannelItem(
+        condition: condition,
+        onCondition: onCondition,
       );
     }).toList();
 
@@ -33,14 +29,25 @@ class OrderPanel extends StatelessWidget {
       ),
     );
   }
+}
+
+class OrderPannelItem extends StatelessWidget {
+  final SortConditionPB condition;
+  final Function(SortConditionPB) onCondition;
+  const OrderPannelItem({
+    required this.condition,
+    required this.onCondition,
+    super.key,
+  });
 
-  String textFromCondition(SortConditionPB condition) {
-    switch (condition) {
-      case SortConditionPB.Ascending:
-        return LocaleKeys.grid_sort_ascending.tr();
-      case SortConditionPB.Descending:
-        return LocaleKeys.grid_sort_descending.tr();
-    }
-    return "";
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      height: GridSize.popoverItemHeight,
+      child: FlowyButton(
+        text: FlowyText.medium(condition.title),
+        onTap: () => onCondition(condition),
+      ),
+    );
   }
 }

+ 23 - 29
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart

@@ -51,12 +51,12 @@ class _SortEditorState extends State<SortEditor> {
               child: Column(
                 children: [
                   _SortList(popoverMutex: popoverMutex),
-                  _AddSortButton(
+                  DatabaseAddSortButton(
                     viewId: widget.viewId,
                     fieldController: widget.fieldController,
                     popoverMutex: popoverMutex,
                   ),
-                  _DeleteSortButton(popoverMutex: popoverMutex),
+                  DatabaseDeleteSortButton(popoverMutex: popoverMutex),
                 ],
               ),
             ),
@@ -79,7 +79,7 @@ class _SortList extends StatelessWidget {
             .map(
               (info) => Padding(
                 padding: const EdgeInsets.symmetric(vertical: 6),
-                child: _SortItem(
+                child: DatabaseSortItem(
                   sortInfo: info,
                   popoverMutex: popoverMutex,
                 ),
@@ -95,10 +95,10 @@ class _SortList extends StatelessWidget {
   }
 }
 
-class _SortItem extends StatelessWidget {
+class DatabaseSortItem extends StatelessWidget {
   final SortInfo sortInfo;
   final PopoverMutex popoverMutex;
-  const _SortItem({
+  const DatabaseSortItem({
     required this.popoverMutex,
     required this.sortInfo,
     Key? key,
@@ -111,7 +111,7 @@ class _SortItem extends StatelessWidget {
       editable: false,
       onTap: () {},
     );
-    final orderButton = _OrderButton(
+    final orderButton = DatabaseSortItemOrderButton(
       sortInfo: sortInfo,
       popoverMutex: popoverMutex,
     );
@@ -141,9 +141,11 @@ class _SortItem extends StatelessWidget {
       ],
     );
   }
+}
 
-  String textFromCondition(SortConditionPB condition) {
-    switch (condition) {
+extension SortConditionExtension on SortConditionPB {
+  String get title {
+    switch (this) {
       case SortConditionPB.Ascending:
         return LocaleKeys.grid_sort_ascending.tr();
       case SortConditionPB.Descending:
@@ -153,11 +155,11 @@ class _SortItem extends StatelessWidget {
   }
 }
 
-class _AddSortButton extends StatefulWidget {
+class DatabaseAddSortButton extends StatefulWidget {
   final String viewId;
   final FieldController fieldController;
   final PopoverMutex popoverMutex;
-  const _AddSortButton({
+  const DatabaseAddSortButton({
     required this.viewId,
     required this.fieldController,
     required this.popoverMutex,
@@ -165,10 +167,10 @@ class _AddSortButton extends StatefulWidget {
   }) : super(key: key);
 
   @override
-  State<_AddSortButton> createState() => _AddSortButtonState();
+  State<DatabaseAddSortButton> createState() => _DatabaseAddSortButtonState();
 }
 
-class _AddSortButtonState extends State<_AddSortButton> {
+class _DatabaseAddSortButtonState extends State<DatabaseAddSortButton> {
   final _popoverController = PopoverController();
 
   @override
@@ -202,9 +204,9 @@ class _AddSortButtonState extends State<_AddSortButton> {
   }
 }
 
-class _DeleteSortButton extends StatelessWidget {
+class DatabaseDeleteSortButton extends StatelessWidget {
   final PopoverMutex popoverMutex;
-  const _DeleteSortButton({required this.popoverMutex, Key? key})
+  const DatabaseDeleteSortButton({required this.popoverMutex, Key? key})
       : super(key: key);
 
   @override
@@ -228,20 +230,22 @@ class _DeleteSortButton extends StatelessWidget {
   }
 }
 
-class _OrderButton extends StatefulWidget {
+class DatabaseSortItemOrderButton extends StatefulWidget {
   final SortInfo sortInfo;
   final PopoverMutex popoverMutex;
-  const _OrderButton({
+  const DatabaseSortItemOrderButton({
     required this.popoverMutex,
     required this.sortInfo,
     Key? key,
   }) : super(key: key);
 
   @override
-  _OrderButtonState createState() => _OrderButtonState();
+  State<DatabaseSortItemOrderButton> createState() =>
+      _DatabaseSortItemOrderButtonState();
 }
 
-class _OrderButtonState extends State<_OrderButton> {
+class _DatabaseSortItemOrderButtonState
+    extends State<DatabaseSortItemOrderButton> {
   final PopoverController popoverController = PopoverController();
 
   @override
@@ -268,20 +272,10 @@ class _OrderButtonState extends State<_OrderButton> {
         );
       },
       child: SortChoiceButton(
-        text: textFromCondition(widget.sortInfo.sortPB.condition),
+        text: widget.sortInfo.sortPB.condition.title,
         rightIcon: arrow,
         onTap: () => popoverController.show(),
       ),
     );
   }
-
-  String textFromCondition(SortConditionPB condition) {
-    switch (condition) {
-      case SortConditionPB.Ascending:
-        return LocaleKeys.grid_sort_ascending.tr();
-      case SortConditionPB.Descending:
-        return LocaleKeys.grid_sort_descending.tr();
-    }
-    return "";
-  }
 }

+ 4 - 5
frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart

@@ -42,6 +42,8 @@ class SettingsLocationCubit extends Cubit<SettingsLocationState> {
   }
 }
 
+const appFlowyDataFolder = "AppFlowyDataDoNotRename";
+
 class ApplicationDataStorage {
   ApplicationDataStorage();
   String? _cachePath;
@@ -55,9 +57,6 @@ class ApplicationDataStorage {
       return;
     }
 
-    // Every custom path will have a folder named `AppFlowyData`
-    const dataFolder = "AppFlowyDataDoNotRename";
-
     if (Platform.isMacOS) {
       // remove the prefix `/Volumes/*`
       path = path.replaceFirst(RegExp(r'^/Volumes/[^/]+'), '');
@@ -68,8 +67,8 @@ class ApplicationDataStorage {
     // If the path is not ends with `AppFlowyData`, we will append the
     // `AppFlowyData` to the path. If the path is ends with `AppFlowyData`,
     // which means the path is the custom path.
-    if (p.basename(path) != dataFolder) {
-      path = p.join(path, dataFolder);
+    if (p.basename(path) != appFlowyDataFolder) {
+      path = p.join(path, appFlowyDataFolder);
     }
 
     // create the directory if not exists.