mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: Add group by field tests
This commit is contained in:
parent
3bbf91ab2b
commit
309bbbd8e7
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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}");
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user