chore: Add group by field tests

This commit is contained in:
Nathan.fooo 2022-10-27 14:11:15 +08:00 committed by GitHub
parent 3bbf91ab2b
commit 309bbbd8e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 275 additions and 105 deletions

View File

@ -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;
}

View File

@ -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,
);
}

View File

@ -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) {
List<Widget> children = [
_switchFieldTypeButton(context, dataController.field)
];
final typeOptionWidget =
_typeOptionWidget(context: context, state: state);
final typeOptionWidget = _typeOptionWidget(
context: context,
state: state,
);
if (typeOptionWidget != null) {
children.add(typeOptionWidget);
}
List<Widget> children = [
_SwitchFieldButton(popoverMutex: popoverMutex),
if (typeOptionWidget != null) 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);
}

View File

@ -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());
await boardResponseFuture();
});
blocTest<FieldEditorBloc, FieldEditorState>(
"switch to text field",
build: () => editorBloc,
wait: boardResponseDuration(),
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<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}");
},
);
});
}

View File

@ -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) {

View File

@ -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;
}
}