Kaynağa Gözat

chore: Add group by field tests

Nathan.fooo 2 yıl önce
ebeveyn
işleme
309bbbd8e7

+ 5 - 0
frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart

@@ -57,6 +57,9 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
               },
             );
           },
+          switchToField: (FieldType fieldType) async {
+            await dataController.switchToField(fieldType);
+          },
         );
       },
     );
@@ -73,6 +76,8 @@ class FieldEditorEvent with _$FieldEditorEvent {
   const factory FieldEditorEvent.initial() = _InitialField;
   const factory FieldEditorEvent.updateName(String name) = _UpdateName;
   const factory FieldEditorEvent.deleteField() = _DeleteField;
+  const factory FieldEditorEvent.switchToField(FieldType fieldType) =
+      _SwitchToField;
   const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) =
       _DidReceiveFieldChanged;
 }

+ 8 - 2
frontend/app_flowy/lib/plugins/grid/application/field/field_type_option_edit_bloc.dart

@@ -25,6 +25,9 @@ class FieldTypeOptionEditBloc
           didReceiveFieldUpdated: (field) {
             emit(state.copyWith(field: field));
           },
+          switchToField: (FieldType fieldType) async {
+            await _dataController.switchToField(fieldType);
+          },
         );
       },
     );
@@ -42,6 +45,8 @@ class FieldTypeOptionEditBloc
 @freezed
 class FieldTypeOptionEditEvent with _$FieldTypeOptionEditEvent {
   const factory FieldTypeOptionEditEvent.initial() = _Initial;
+  const factory FieldTypeOptionEditEvent.switchToField(FieldType fieldType) =
+      _SwitchToField;
   const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(FieldPB field) =
       _DidReceiveFieldUpdated;
 }
@@ -53,8 +58,9 @@ class FieldTypeOptionEditState with _$FieldTypeOptionEditState {
   }) = _FieldTypeOptionEditState;
 
   factory FieldTypeOptionEditState.initial(
-          TypeOptionDataController fieldContext) =>
+    TypeOptionDataController typeOptionDataController,
+  ) =>
       FieldTypeOptionEditState(
-        field: fieldContext.field,
+        field: typeOptionDataController.field,
       );
 }

+ 67 - 40
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_option_editor.dart

@@ -25,31 +25,35 @@ typedef SwitchToFieldCallback
 );
 
 class FieldTypeOptionEditor extends StatelessWidget {
-  final TypeOptionDataController dataController;
+  final TypeOptionDataController _dataController;
   final PopoverMutex popoverMutex;
 
   const FieldTypeOptionEditor({
-    required this.dataController,
+    required TypeOptionDataController dataController,
     required this.popoverMutex,
     Key? key,
-  }) : super(key: key);
+  })  : _dataController = dataController,
+        super(key: key);
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => FieldTypeOptionEditBloc(dataController)
-        ..add(const FieldTypeOptionEditEvent.initial()),
+      create: (context) {
+        final bloc = FieldTypeOptionEditBloc(_dataController);
+        bloc.add(const FieldTypeOptionEditEvent.initial());
+        return bloc;
+      },
       child: BlocBuilder<FieldTypeOptionEditBloc, FieldTypeOptionEditState>(
         builder: (context, state) {
+          final typeOptionWidget = _typeOptionWidget(
+            context: context,
+            state: state,
+          );
+
           List<Widget> children = [
-            _switchFieldTypeButton(context, dataController.field)
+            _SwitchFieldButton(popoverMutex: popoverMutex),
+            if (typeOptionWidget != null) typeOptionWidget
           ];
-          final typeOptionWidget =
-              _typeOptionWidget(context: context, state: state);
-
-          if (typeOptionWidget != null) {
-            children.add(typeOptionWidget);
-          }
 
           return ListView(
             shrinkWrap: true,
@@ -60,45 +64,68 @@ class FieldTypeOptionEditor extends StatelessWidget {
     );
   }
 
-  Widget _switchFieldTypeButton(BuildContext context, FieldPB field) {
-    final theme = context.watch<AppTheme>();
-    return SizedBox(
-      height: GridSize.typeOptionItemHeight,
-      child: AppFlowyPopover(
-        constraints: BoxConstraints.loose(const Size(460, 540)),
-        asBarrier: true,
-        triggerActions: PopoverTriggerFlags.click | PopoverTriggerFlags.hover,
-        mutex: popoverMutex,
-        offset: const Offset(20, 0),
-        popupBuilder: (context) {
-          return FieldTypeList(onSelectField: (newFieldType) {
-            dataController.switchToField(newFieldType);
-          });
-        },
-        child: FlowyButton(
-          text: FlowyText.medium(field.fieldType.title(), fontSize: 12),
-          margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
-          hoverColor: theme.hover,
-          leftIcon:
-              svgWidget(field.fieldType.iconName(), color: theme.iconColor),
-          rightIcon: svgWidget("grid/more", color: theme.iconColor),
-        ),
-      ),
-    );
-  }
-
   Widget? _typeOptionWidget({
     required BuildContext context,
     required FieldTypeOptionEditState state,
   }) {
     return makeTypeOptionWidget(
       context: context,
-      dataController: dataController,
+      dataController: _dataController,
       popoverMutex: popoverMutex,
     );
   }
 }
 
+class _SwitchFieldButton extends StatelessWidget {
+  final PopoverMutex popoverMutex;
+  const _SwitchFieldButton({
+    required this.popoverMutex,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final widget = AppFlowyPopover(
+      constraints: BoxConstraints.loose(const Size(460, 540)),
+      asBarrier: true,
+      triggerActions: PopoverTriggerFlags.click | PopoverTriggerFlags.hover,
+      mutex: popoverMutex,
+      offset: const Offset(20, 0),
+      popupBuilder: (popOverContext) {
+        return FieldTypeList(onSelectField: (newFieldType) {
+          context
+              .read<FieldTypeOptionEditBloc>()
+              .add(FieldTypeOptionEditEvent.switchToField(newFieldType));
+        });
+      },
+      child: _buildMoreButton(context),
+    );
+
+    return SizedBox(
+      height: GridSize.typeOptionItemHeight,
+      child: widget,
+    );
+  }
+
+  Widget _buildMoreButton(BuildContext context) {
+    final theme = context.read<AppTheme>();
+    final bloc = context.read<FieldTypeOptionEditBloc>();
+    return FlowyButton(
+      text: FlowyText.medium(
+        bloc.state.field.fieldType.title(),
+        fontSize: 12,
+      ),
+      margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
+      hoverColor: theme.hover,
+      leftIcon: svgWidget(
+        bloc.state.field.fieldType.iconName(),
+        color: theme.iconColor,
+      ),
+      rightIcon: svgWidget("grid/more", color: theme.iconColor),
+    );
+  }
+}
+
 abstract class TypeOptionWidget extends StatelessWidget {
   const TypeOptionWidget({Key? key}) : super(key: key);
 }

+ 138 - 21
frontend/app_flowy/test/bloc_test/board_test/group_by_field_test.dart

@@ -1,8 +1,10 @@
 import 'package:app_flowy/plugins/board/application/board_bloc.dart';
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/setting/group_bloc.dart';
 import 'package:bloc_test/bloc_test.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pbserver.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter_test/flutter_test.dart';
 
 import 'util.dart';
@@ -14,41 +16,117 @@ void main() {
     boardTest = await AppFlowyBoardTest.ensureInitialized();
   });
 
-  // Group with not support grouping field
-  group('Group with not support grouping field:', () {
-    late FieldEditorBloc editorBloc;
+  // Group by multi-select with no options
+  group('Group by multi-select with no options', () {
+    //
+    late FieldPB multiSelectField;
+    late String expectedGroupName;
+
     setUpAll(() async {
       await boardTest.context.createTestBoard();
-      final fieldContext = boardTest.context.singleSelectFieldContext();
-      editorBloc = boardTest.context.createFieldEditor(
-        fieldContext: fieldContext,
-      )..add(const FieldEditorEvent.initial());
+    });
 
+    test('create multi-select field', () async {
+      await boardTest.context.createField(FieldType.MultiSelect);
       await boardResponseFuture();
+
+      assert(boardTest.context.fieldContexts.length == 3);
+      multiSelectField = boardTest.context.fieldContexts.last.field;
+      expectedGroupName = "No ${multiSelectField.name}";
+      assert(multiSelectField.fieldType == FieldType.MultiSelect);
     });
 
-    blocTest<FieldEditorBloc, FieldEditorState>(
-      "switch to text field",
-      build: () => editorBloc,
-      wait: boardResponseDuration(),
+    blocTest<GridGroupBloc, GridGroupState>(
+      "set grouped by multi-select field",
+      build: () => GridGroupBloc(
+        viewId: boardTest.context.gridView.id,
+        fieldController: boardTest.context.fieldController,
+      ),
       act: (bloc) async {
-        await bloc.dataController.switchToField(FieldType.RichText);
-      },
-      verify: (bloc) {
-        bloc.state.field.fold(
-          () => throw Exception(),
-          (field) => field.fieldType == FieldType.RichText,
-        );
+        bloc.add(GridGroupEvent.setGroupByField(
+          multiSelectField.id,
+          multiSelectField.fieldType,
+        ));
       },
+      wait: boardResponseDuration(),
     );
+
     blocTest<BoardBloc, BoardState>(
-      'assert the number of groups is 1',
+      "assert only have the 'No status' group",
       build: () => BoardBloc(view: boardTest.context.gridView)
         ..add(const BoardEvent.initial()),
       wait: boardResponseDuration(),
       verify: (bloc) {
         assert(bloc.groupControllers.values.length == 1,
             "Expected 1, but receive ${bloc.groupControllers.values.length}");
+
+        assert(
+            bloc.groupControllers.values.first.group.desc == expectedGroupName,
+            "Expected $expectedGroupName, but receive ${bloc.groupControllers.values.first.group.desc}");
+      },
+    );
+  });
+
+  group('Group by multi-select with two options', () {
+    late FieldPB multiSelectField;
+
+    setUpAll(() async {
+      await boardTest.context.createTestBoard();
+    });
+
+    test('create multi-select field', () async {
+      await boardTest.context.createField(FieldType.MultiSelect);
+      await boardResponseFuture();
+
+      assert(boardTest.context.fieldContexts.length == 3);
+      multiSelectField = boardTest.context.fieldContexts.last.field;
+      assert(multiSelectField.fieldType == FieldType.MultiSelect);
+
+      final cellController =
+          await boardTest.context.makeCellController(multiSelectField.id)
+              as GridSelectOptionCellController;
+
+      final multiSelectOptionBloc =
+          SelectOptionCellEditorBloc(cellController: cellController);
+      multiSelectOptionBloc.add(const SelectOptionEditorEvent.initial());
+      await boardResponseFuture();
+
+      multiSelectOptionBloc.add(const SelectOptionEditorEvent.newOption("A"));
+      await boardResponseFuture();
+
+      multiSelectOptionBloc.add(const SelectOptionEditorEvent.newOption("B"));
+      await boardResponseFuture();
+    });
+
+    blocTest<GridGroupBloc, GridGroupState>(
+      "set grouped by multi-select field",
+      build: () => GridGroupBloc(
+        viewId: boardTest.context.gridView.id,
+        fieldController: boardTest.context.fieldController,
+      ),
+      act: (bloc) async {
+        bloc.add(GridGroupEvent.setGroupByField(
+          multiSelectField.id,
+          multiSelectField.fieldType,
+        ));
+      },
+      wait: boardResponseDuration(),
+    );
+
+    blocTest<BoardBloc, BoardState>(
+      "check the groups' order",
+      build: () => BoardBloc(view: boardTest.context.gridView)
+        ..add(const BoardEvent.initial()),
+      wait: boardResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.groupControllers.values.length == 3,
+            "Expected 3, but receive ${bloc.groupControllers.values.length}");
+
+        final groups =
+            bloc.groupControllers.values.map((e) => e.group).toList();
+        assert(groups[0].desc == "No ${multiSelectField.name}");
+        assert(groups[1].desc == "B");
+        assert(groups[2].desc == "A");
       },
     );
   });
@@ -78,7 +156,7 @@ void main() {
     );
 
     test('create checkbox field', () async {
-      await boardTest.context.createFieldFromType(FieldType.Checkbox);
+      await boardTest.context.createField(FieldType.Checkbox);
       await boardResponseFuture();
 
       assert(boardTest.context.fieldContexts.length == 3);
@@ -110,4 +188,43 @@ void main() {
       },
     );
   });
+
+  // Group with not support grouping field
+  group('Group with not support grouping field:', () {
+    late FieldEditorBloc editorBloc;
+    setUpAll(() async {
+      await boardTest.context.createTestBoard();
+      final fieldContext = boardTest.context.singleSelectFieldContext();
+      editorBloc = boardTest.context.createFieldEditor(
+        fieldContext: fieldContext,
+      )..add(const FieldEditorEvent.initial());
+
+      await boardResponseFuture();
+    });
+
+    blocTest<FieldEditorBloc, FieldEditorState>(
+      "switch to text field",
+      build: () => editorBloc,
+      wait: boardResponseDuration(),
+      act: (bloc) async {
+        bloc.add(const FieldEditorEvent.switchToField(FieldType.RichText));
+      },
+      verify: (bloc) {
+        bloc.state.field.fold(
+          () => throw Exception(),
+          (field) => field.fieldType == FieldType.RichText,
+        );
+      },
+    );
+    blocTest<BoardBloc, BoardState>(
+      'assert the number of groups is 1',
+      build: () => BoardBloc(view: boardTest.context.gridView)
+        ..add(const BoardEvent.initial()),
+      wait: boardResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.groupControllers.values.length == 1,
+            "Expected 1, but receive ${bloc.groupControllers.values.length}");
+      },
+    );
+  });
 }

+ 2 - 1
frontend/app_flowy/test/bloc_test/grid_test/field_edit_bloc_test.dart

@@ -55,7 +55,8 @@ void main() {
       "switch to text field",
       build: () => editorBloc,
       act: (bloc) async {
-        editorBloc.dataController.switchToField(FieldType.RichText);
+        editorBloc
+            .add(const FieldEditorEvent.switchToField(FieldType.RichText));
       },
       wait: gridResponseDuration(),
       verify: (bloc) {

+ 52 - 38
frontend/app_flowy/test/bloc_test/grid_test/util.dart

@@ -98,12 +98,52 @@ class AppFlowyGridTest {
     return editorBloc;
   }
 
-  Future<FieldEditorBloc> createFieldFromType(FieldType fieldType) async {
-    final editor = createFieldEditor()..add(const FieldEditorEvent.initial());
+  Future<IGridCellController> makeCellController(String fieldId) async {
+    final builder = await makeCellControllerBuilder(fieldId);
+    return builder.build();
+  }
+
+  Future<GridCellControllerBuilder> makeCellControllerBuilder(
+    String fieldId,
+  ) async {
+    final RowInfo rowInfo = rowInfos.last;
+    final blockCache = blocks[rowInfo.rowPB.blockId];
+    final rowCache = blockCache?.rowCache;
+    late GridFieldController fieldController;
+    if (_gridDataController != null) {
+      fieldController = _gridDataController!.fieldController;
+    }
+
+    if (_boardDataController != null) {
+      fieldController = _boardDataController!.fieldController;
+    }
+
+    final rowDataController = GridRowDataController(
+      rowInfo: rowInfo,
+      fieldController: fieldController,
+      rowCache: rowCache!,
+    );
+
+    final rowBloc = RowBloc(
+      rowInfo: rowInfo,
+      dataController: rowDataController,
+    )..add(const RowEvent.initial());
     await gridResponseFuture();
-    editor.dataController.switchToField(fieldType);
+
+    return GridCellControllerBuilder(
+      cellId: rowBloc.state.gridCellMap[fieldId]!,
+      cellCache: rowCache.cellCache,
+      delegate: rowDataController,
+    );
+  }
+
+  Future<FieldEditorBloc> createField(FieldType fieldType) async {
+    final editorBloc = createFieldEditor()
+      ..add(const FieldEditorEvent.initial());
     await gridResponseFuture();
-    return Future(() => editor);
+    editorBloc.add(FieldEditorEvent.switchToField(fieldType));
+    await gridResponseFuture();
+    return Future(() => editorBloc);
   }
 
   GridFieldContext singleSelectFieldContext() {
@@ -162,46 +202,20 @@ class AppFlowyGridTest {
 
 /// Create a new Grid for cell test
 class AppFlowyGridCellTest {
-  final AppFlowyGridTest _gridTest;
-  AppFlowyGridCellTest(AppFlowyGridTest gridTest) : _gridTest = gridTest;
+  final AppFlowyGridTest gridTest;
+  AppFlowyGridCellTest({required this.gridTest});
 
   static Future<AppFlowyGridCellTest> ensureInitialized() async {
     final gridTest = await AppFlowyGridTest.ensureInitialized();
-    return AppFlowyGridCellTest(gridTest);
+    return AppFlowyGridCellTest(gridTest: gridTest);
   }
 
   Future<void> createTestRow() async {
-    await _gridTest.createRow();
+    await gridTest.createRow();
   }
 
   Future<void> createTestGrid() async {
-    await _gridTest.createTestGrid();
-  }
-
-  Future<GridCellControllerBuilder> cellControllerBuilder(
-    String fieldId,
-  ) async {
-    final RowInfo rowInfo = _gridTest.rowInfos.last;
-    final blockCache = _gridTest.blocks[rowInfo.rowPB.blockId];
-    final rowCache = blockCache?.rowCache;
-
-    final rowDataController = GridRowDataController(
-      rowInfo: rowInfo,
-      fieldController: _gridTest._gridDataController!.fieldController,
-      rowCache: rowCache!,
-    );
-
-    final rowBloc = RowBloc(
-      rowInfo: rowInfo,
-      dataController: rowDataController,
-    )..add(const RowEvent.initial());
-    await gridResponseFuture();
-
-    return GridCellControllerBuilder(
-      cellId: rowBloc.state.gridCellMap[fieldId]!,
-      cellCache: rowCache.cellCache,
-      delegate: rowDataController,
-    );
+    await gridTest.createTestGrid();
   }
 }
 
@@ -229,11 +243,11 @@ class AppFlowyGridSelectOptionCellTest {
     assert(fieldType == FieldType.SingleSelect ||
         fieldType == FieldType.MultiSelect);
 
-    final fieldContexts = _gridCellTest._gridTest.fieldContexts;
+    final fieldContexts = _gridCellTest.gridTest.fieldContexts;
     final field =
         fieldContexts.firstWhere((element) => element.fieldType == fieldType);
-    final builder = await _gridCellTest.cellControllerBuilder(field.id);
-    final cellController = builder.build() as GridSelectOptionCellController;
+    final cellController = await _gridCellTest.gridTest
+        .makeCellController(field.id) as GridSelectOptionCellController;
     return cellController;
   }
 }