From 9ee39f45c93a0da5b5a00a0d39a96881b4df230c Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:41:20 +0800 Subject: [PATCH] test: rewrite bloc tests (#6492) --- .../application/field/field_controller.dart | 37 +- .../application/field/filter_entities.dart | 100 ++- .../settings/share/import_service.dart | 5 +- .../cell/select_option_cell_test.dart | 69 +- .../grid_test/cell/text_cell_bloc_test.dart | 109 +++ .../grid_test/field/edit_field_test.dart | 43 -- .../grid_test/field/field_cell_bloc_test.dart | 14 +- .../field/field_editor_bloc_test.dart | 133 ++++ .../grid_test/filter/create_filter_test.dart | 159 ----- ...test.dart => filter_editor_bloc_test.dart} | 117 ++-- .../filter/filter_entities_test.dart | 623 ++++++++++++++++++ .../filter/filter_rows_by_checkbox_test.dart | 55 -- .../filter/filter_rows_by_text_test.dart | 165 ----- .../grid_test/filter/filter_util.dart | 42 -- .../bloc_test/grid_test/grid_bloc_test.dart | 33 +- .../grid_test/sort/sort_editor_bloc_test.dart | 204 ++++++ .../test/bloc_test/grid_test/util.dart | 192 +++--- frontend/appflowy_flutter/test/util.dart | 4 +- 18 files changed, 1363 insertions(+), 741 deletions(-) create mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/cell/text_cell_bloc_test.dart delete mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart create mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/field/field_editor_bloc_test.dart delete mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart rename frontend/appflowy_flutter/test/bloc_test/grid_test/filter/{filter_menu_test.dart => filter_editor_bloc_test.dart} (64%) create mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_entities_test.dart delete mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_checkbox_test.dart delete mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart delete mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart create mode 100644 frontend/appflowy_flutter/test/bloc_test/grid_test/sort/sort_editor_bloc_test.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart index b9ac7c6bb4..8370bd9bff 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart @@ -174,7 +174,8 @@ class FieldController { (FilterChangesetNotificationPB changeset) { _filterNotifier?.filters = _filterListFromPBs(changeset.filters.items); - _updateFieldInfos(); + _fieldNotifier.fieldInfos = + _updateFieldInfos(_fieldNotifier.fieldInfos); }, (err) => Log.error(err), ); @@ -396,7 +397,7 @@ class FieldController { (updatedFields, fieldInfos) = await updateFields(changeset.updatedFields, fieldInfos); - _fieldNotifier.fieldInfos = fieldInfos; + _fieldNotifier.fieldInfos = _updateFieldInfos(fieldInfos); for (final listener in _updatedFieldCallbacks.values) { listener(updatedFields); } @@ -465,25 +466,22 @@ class FieldController { _fieldSettings.clear(); _fieldSettings.addAll(setting.fieldSettings.items); - _updateFieldInfos(); + _fieldNotifier.fieldInfos = _updateFieldInfos(_fieldNotifier.fieldInfos); } /// Attach sort, filter, group information and field settings to `FieldInfo` - void _updateFieldInfos() { - final List newFieldInfos = []; - for (final field in _fieldNotifier.fieldInfos) { - newFieldInfos.add( - field.copyWith( - fieldSettings: _fieldSettings - .firstWhereOrNull((setting) => setting.fieldId == field.id), - isGroupField: _groupConfigurationByFieldId[field.id] != null, - hasFilter: getFilterByFieldId(field.id) != null, - hasSort: getSortByFieldId(field.id) != null, - ), - ); - } - - _fieldNotifier.fieldInfos = newFieldInfos; + List _updateFieldInfos(List fieldInfos) { + return fieldInfos + .map( + (field) => field.copyWith( + fieldSettings: _fieldSettings + .firstWhereOrNull((setting) => setting.fieldId == field.id), + isGroupField: _groupConfigurationByFieldId[field.id] != null, + hasFilter: getFilterByFieldId(field.id) != null, + hasSort: getSortByFieldId(field.id) != null, + ), + ) + .toList(); } /// Load all of the fields. This is required when opening the database @@ -506,7 +504,8 @@ class FieldController { _loadAllFieldSettings(), _loadSettings(), ]); - _updateFieldInfos(); + _fieldNotifier.fieldInfos = + _updateFieldInfos(_fieldNotifier.fieldInfos); return FlowyResult.success(null); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/filter_entities.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/filter_entities.dart index b9abb24bf6..e93893154d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/filter_entities.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/filter_entities.dart @@ -14,11 +14,12 @@ import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart'; import 'package:appflowy/util/int64_extension.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:equatable/equatable.dart'; import 'package:fixnum/fixnum.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/widgets.dart'; -abstract class DatabaseFilter { +abstract class DatabaseFilter extends Equatable { const DatabaseFilter({ required this.filterId, required this.fieldId, @@ -112,16 +113,18 @@ abstract class DatabaseFilter { } final class TextFilter extends DatabaseFilter { - const TextFilter({ + TextFilter({ required super.filterId, required super.fieldId, required super.fieldType, required this.condition, - required this.content, - }); + required String content, + }) { + this.content = canAttachContent ? content : ""; + } final TextFilterConditionPB condition; - final String content; + late final String content; @override String get conditionName => condition.filterName; @@ -182,19 +185,24 @@ final class TextFilter extends DatabaseFilter { content: content ?? this.content, ); } + + @override + List get props => [filterId, fieldId, condition, content]; } final class NumberFilter extends DatabaseFilter { - const NumberFilter({ + NumberFilter({ required super.filterId, required super.fieldId, required super.fieldType, required this.condition, - required this.content, - }); + required String content, + }) { + this.content = canAttachContent ? content : ""; + } final NumberFilterConditionPB condition; - final String content; + late final String content; @override String get conditionName => condition.filterName; @@ -253,6 +261,9 @@ final class NumberFilter extends DatabaseFilter { content: content ?? this.content, ); } + + @override + List get props => [filterId, fieldId, condition, content]; } final class CheckboxFilter extends DatabaseFilter { @@ -289,6 +300,9 @@ final class CheckboxFilter extends DatabaseFilter { condition: condition ?? this.condition, ); } + + @override + List get props => [filterId, fieldId, condition]; } final class ChecklistFilter extends DatabaseFilter { @@ -325,19 +339,33 @@ final class ChecklistFilter extends DatabaseFilter { condition: condition ?? this.condition, ); } + + @override + List get props => [filterId, fieldId, condition]; } final class SelectOptionFilter extends DatabaseFilter { - const SelectOptionFilter({ + SelectOptionFilter({ required super.filterId, required super.fieldId, required super.fieldType, required this.condition, - required this.optionIds, - }); + required List optionIds, + }) { + if (canAttachContent) { + if (fieldType == FieldType.SingleSelect && + (condition == SelectOptionFilterConditionPB.OptionIs || + condition == SelectOptionFilterConditionPB.OptionIsNot) && + optionIds.isNotEmpty) { + this.optionIds.add(optionIds.first); + } else { + this.optionIds.addAll(optionIds); + } + } + } final SelectOptionFilterConditionPB condition; - final List optionIds; + final List optionIds = []; @override String get conditionName => condition.i18n; @@ -428,6 +456,9 @@ final class SelectOptionFilter extends DatabaseFilter { field.fieldType == FieldType.SingleSelect ? const SingleSelectOptionFilterDelegateImpl() : const MultiSelectOptionFilterDelegateImpl(); + + @override + List get props => [filterId, fieldId, condition, optionIds]; } enum DateTimeFilterCondition { @@ -491,7 +522,8 @@ enum DateTimeFilterCondition { } static List availableConditionsForFieldType( - FieldType fieldType,) { + FieldType fieldType, + ) { final result = [...values]; if (fieldType == FieldType.CreatedTime || fieldType == FieldType.LastEditedTime) { @@ -504,20 +536,37 @@ enum DateTimeFilterCondition { } final class DateTimeFilter extends DatabaseFilter { - const DateTimeFilter({ + DateTimeFilter({ required super.filterId, required super.fieldId, required super.fieldType, required this.condition, - this.timestamp, - this.start, - this.end, - }); + DateTime? timestamp, + DateTime? start, + DateTime? end, + }) { + if (canAttachContent) { + if (condition == DateFilterConditionPB.DateStartsBetween || + condition == DateFilterConditionPB.DateEndsBetween) { + this.start = start; + this.end = end; + this.timestamp = null; + } else { + this.timestamp = timestamp; + this.start = null; + this.end = null; + } + } else { + this.timestamp = null; + this.start = null; + this.end = null; + } + } final DateFilterConditionPB condition; - final DateTime? timestamp; - final DateTime? start; - final DateTime? end; + late final DateTime? timestamp; + late final DateTime? start; + late final DateTime? end; @override String get conditionName => condition.toCondition().filterName; @@ -654,6 +703,10 @@ final class DateTimeFilter extends DatabaseFilter { timestamp: timestamp, ); } + + @override + List get props => + [filterId, fieldId, condition, timestamp, start, end]; } final class TimeFilter extends DatabaseFilter { @@ -703,4 +756,7 @@ final class TimeFilter extends DatabaseFilter { content: content ?? this.content, ); } + + @override + List get props => [filterId, fieldId, condition, content]; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart index 17878d56bc..3a323abce0 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart @@ -1,7 +1,6 @@ import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/import.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_result/appflowy_result.dart'; class ImportPayload { @@ -17,7 +16,7 @@ class ImportPayload { } class ImportBackendService { - static Future> importPages( + static Future> importPages( String parentViewId, List values, ) async { diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/select_option_cell_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/select_option_cell_test.dart index fa2e6fb71c..ae7c2d6bec 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/select_option_cell_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/select_option_cell_test.dart @@ -1,4 +1,6 @@ import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart'; +import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database/application/row/row_service.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -6,20 +8,25 @@ import 'package:flutter_test/flutter_test.dart'; import '../util.dart'; void main() { - late AppFlowyGridCellTest cellTest; + late AppFlowyGridTest cellTest; + setUpAll(() async { - cellTest = await AppFlowyGridCellTest.ensureInitialized(); + cellTest = await AppFlowyGridTest.ensureInitialized(); }); - group('SingleSelectOptionBloc', () { - test('create options', () async { - await cellTest.createTestGrid(); - await cellTest.createTestRow(); - final cellController = cellTest.makeSelectOptionCellController( - FieldType.SingleSelect, - 0, - ); + group('select cell bloc:', () { + late GridTestContext context; + late SelectOptionCellController cellController; + setUp(() async { + context = await cellTest.makeDefaultTestGrid(); + await RowBackendService.createRow(viewId: context.view.id); + final fieldIndex = context.fieldController.fieldInfos + .indexWhere((field) => field.fieldType == FieldType.SingleSelect); + cellController = context.makeGridCellController(fieldIndex, 0).as(); + }); + + test('create options', () async { final bloc = SelectOptionCellEditorBloc(cellController: cellController); await gridResponseFuture(); @@ -32,13 +39,6 @@ void main() { }); test('update options', () async { - await cellTest.createTestGrid(); - await cellTest.createTestRow(); - final cellController = cellTest.makeSelectOptionCellController( - FieldType.SingleSelect, - 0, - ); - final bloc = SelectOptionCellEditorBloc(cellController: cellController); await gridResponseFuture(); @@ -57,13 +57,6 @@ void main() { }); test('delete options', () async { - await cellTest.createTestGrid(); - await cellTest.createTestRow(); - final cellController = cellTest.makeSelectOptionCellController( - FieldType.SingleSelect, - 0, - ); - final bloc = SelectOptionCellEditorBloc(cellController: cellController); await gridResponseFuture(); @@ -108,13 +101,6 @@ void main() { }); test('select/unselect option', () async { - await cellTest.createTestGrid(); - await cellTest.createTestRow(); - final cellController = cellTest.makeSelectOptionCellController( - FieldType.SingleSelect, - 0, - ); - final bloc = SelectOptionCellEditorBloc(cellController: cellController); await gridResponseFuture(); @@ -135,13 +121,6 @@ void main() { }); test('select an option or create one', () async { - await cellTest.createTestGrid(); - await cellTest.createTestRow(); - final cellController = cellTest.makeSelectOptionCellController( - FieldType.SingleSelect, - 0, - ); - final bloc = SelectOptionCellEditorBloc(cellController: cellController); await gridResponseFuture(); @@ -163,13 +142,6 @@ void main() { }); test('select multiple options', () async { - await cellTest.createTestGrid(); - await cellTest.createTestRow(); - final cellController = cellTest.makeSelectOptionCellController( - FieldType.SingleSelect, - 0, - ); - final bloc = SelectOptionCellEditorBloc(cellController: cellController); await gridResponseFuture(); @@ -195,13 +167,6 @@ void main() { }); test('filter options', () async { - await cellTest.createTestGrid(); - await cellTest.createTestRow(); - final cellController = cellTest.makeSelectOptionCellController( - FieldType.SingleSelect, - 0, - ); - final bloc = SelectOptionCellEditorBloc(cellController: cellController); await gridResponseFuture(); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/text_cell_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/text_cell_bloc_test.dart new file mode 100644 index 0000000000..699c129e5f --- /dev/null +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/text_cell_bloc_test.dart @@ -0,0 +1,109 @@ +import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart'; +import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database/application/row/row_service.dart'; +import 'package:appflowy/plugins/database/domain/field_service.dart'; +import 'package:appflowy/plugins/database/domain/field_settings_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../util.dart'; + +void main() { + late AppFlowyGridTest cellTest; + + setUpAll(() async { + cellTest = await AppFlowyGridTest.ensureInitialized(); + }); + + group('text cell bloc:', () { + late GridTestContext context; + late TextCellController cellController; + + setUp(() async { + context = await cellTest.makeDefaultTestGrid(); + await RowBackendService.createRow(viewId: context.view.id); + final fieldIndex = context.fieldController.fieldInfos + .indexWhere((field) => field.fieldType == FieldType.RichText); + cellController = context.makeGridCellController(fieldIndex, 0).as(); + }); + + test('update text', () async { + final bloc = TextCellBloc(cellController: cellController); + await gridResponseFuture(); + + expect(bloc.state.content, ""); + + bloc.add(const TextCellEvent.updateText("A")); + await gridResponseFuture(milliseconds: 600); + + expect(bloc.state.content, "A"); + }); + + test('non-primary text field emoji and hasDocument', () async { + final primaryBloc = TextCellBloc(cellController: cellController); + expect(primaryBloc.state.emoji == null, false); + expect(primaryBloc.state.hasDocument == null, false); + + await primaryBloc.close(); + + await FieldBackendService.createField( + viewId: context.view.id, + fieldName: "Second", + ); + await gridResponseFuture(); + final fieldIndex = context.fieldController.fieldInfos.indexWhere( + (field) => field.fieldType == FieldType.RichText && !field.isPrimary, + ); + cellController = context.makeGridCellController(fieldIndex, 0).as(); + final nonPrimaryBloc = TextCellBloc(cellController: cellController); + await gridResponseFuture(); + + expect(nonPrimaryBloc.state.emoji == null, true); + expect(nonPrimaryBloc.state.hasDocument == null, true); + }); + + test('update wrap cell content', () async { + final bloc = TextCellBloc(cellController: cellController); + await gridResponseFuture(); + + expect(bloc.state.wrap, true); + + await FieldSettingsBackendService( + viewId: context.view.id, + ).updateFieldSettings( + fieldId: cellController.fieldId, + wrapCellContent: false, + ); + await gridResponseFuture(); + + expect(bloc.state.wrap, false); + }); + + test('update emoji', () async { + final bloc = TextCellBloc(cellController: cellController); + await gridResponseFuture(); + + expect(bloc.state.emoji!.value, ""); + + await RowBackendService(viewId: context.view.id) + .updateMeta(rowId: cellController.rowId, iconURL: "dummy"); + await gridResponseFuture(); + + expect(bloc.state.emoji!.value, "dummy"); + }); + + test('update document data', () async { + // This is so fake? + final bloc = TextCellBloc(cellController: cellController); + await gridResponseFuture(); + + expect(bloc.state.hasDocument!.value, false); + + await RowBackendService(viewId: context.view.id) + .updateMeta(rowId: cellController.rowId, isDocumentEmpty: false); + await gridResponseFuture(); + + expect(bloc.state.hasDocument!.value, true); + }); + }); +} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart deleted file mode 100644 index 4af2137c89..0000000000 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:appflowy/plugins/database/application/field/field_editor_bloc.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../util.dart'; - -Future createEditorBloc(AppFlowyGridTest gridTest) async { - final context = await gridTest.createTestGrid(); - final fieldInfo = context.getSelectOptionField(); - return FieldEditorBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, - fieldInfo: fieldInfo, - isNew: false, - ); -} - -void main() { - late AppFlowyGridTest gridTest; - - setUpAll(() async { - gridTest = await AppFlowyGridTest.ensureInitialized(); - }); - - test('rename field', () async { - final editorBloc = await createEditorBloc(gridTest); - editorBloc.add(const FieldEditorEvent.renameField('Hello world')); - - await gridResponseFuture(); - expect(editorBloc.state.field.name, equals("Hello world")); - }); - - test('switch to text field', () async { - final editorBloc = await createEditorBloc(gridTest); - - editorBloc.add(const FieldEditorEvent.switchFieldType(FieldType.RichText)); - await gridResponseFuture(); - - // The default length of the fields is 3. The length of the fields - // should not change after switching to other field type - expect(editorBloc.state.field.fieldType, equals(FieldType.RichText)); - }); -} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/field_cell_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/field_cell_bloc_test.dart index c9344a4917..334f60ee4c 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/field_cell_bloc_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/field_cell_bloc_test.dart @@ -11,19 +11,19 @@ void main() { gridTest = await AppFlowyGridTest.ensureInitialized(); }); - group('$FieldCellBloc', () { + group('field cell bloc:', () { late GridTestContext context; late double width; setUp(() async { - context = await gridTest.createTestGrid(); + context = await gridTest.makeDefaultTestGrid(); }); blocTest( 'update field width', build: () => FieldCellBloc( - fieldInfo: context.fieldInfos[0], - viewId: context.gridView.id, + fieldInfo: context.fieldController.fieldInfos[0], + viewId: context.view.id, ), act: (bloc) { width = bloc.state.width; @@ -37,10 +37,10 @@ void main() { ); blocTest( - 'field width should not be lesser than 50px', + 'field width should not be less than 50px', build: () => FieldCellBloc( - viewId: context.gridView.id, - fieldInfo: context.fieldInfos[0], + viewId: context.view.id, + fieldInfo: context.fieldController.fieldInfos[0], ), act: (bloc) { bloc.add(const FieldCellEvent.onResizeStart()); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/field_editor_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/field_editor_bloc_test.dart new file mode 100644 index 0000000000..f5c36f1014 --- /dev/null +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/field_editor_bloc_test.dart @@ -0,0 +1,133 @@ +import 'package:appflowy/plugins/database/application/field/field_editor_bloc.dart'; +import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:nanoid/nanoid.dart'; + +import '../util.dart'; + +void main() { + late AppFlowyGridTest gridTest; + + setUpAll(() async { + gridTest = await AppFlowyGridTest.ensureInitialized(); + }); + + group('field editor bloc:', () { + late GridTestContext context; + late FieldEditorBloc editorBloc; + + setUp(() async { + context = await gridTest.makeDefaultTestGrid(); + final fieldInfo = context.fieldController.fieldInfos + .firstWhere((field) => field.fieldType == FieldType.SingleSelect); + editorBloc = FieldEditorBloc( + viewId: context.view.id, + fieldController: context.fieldController, + fieldInfo: fieldInfo, + isNew: false, + ); + }); + + test('rename field', () async { + expect(editorBloc.state.field.name, equals("Type")); + + editorBloc.add(const FieldEditorEvent.renameField('Hello world')); + + await gridResponseFuture(); + expect(editorBloc.state.field.name, equals("Hello world")); + }); + + test('edit icon', () async { + expect(editorBloc.state.field.icon, equals("")); + + editorBloc.add(const FieldEditorEvent.updateIcon('emoji/smiley-face')); + + await gridResponseFuture(); + expect(editorBloc.state.field.icon, equals("emoji/smiley-face")); + + editorBloc.add(const FieldEditorEvent.updateIcon("")); + + await gridResponseFuture(); + expect(editorBloc.state.field.icon, equals("")); + }); + + test('switch to text field', () async { + expect(editorBloc.state.field.fieldType, equals(FieldType.SingleSelect)); + + editorBloc.add( + const FieldEditorEvent.switchFieldType(FieldType.RichText), + ); + await gridResponseFuture(); + + expect(editorBloc.state.field.fieldType, equals(FieldType.RichText)); + }); + + test('update field type option', () async { + final selectOption = SelectOptionPB() + ..id = nanoid(4) + ..color = SelectOptionColorPB.Lime + ..name = "New option"; + final typeOptionData = SingleSelectTypeOptionPB() + ..options.addAll([selectOption]); + + editorBloc.add( + FieldEditorEvent.updateTypeOption(typeOptionData.writeToBuffer()), + ); + await gridResponseFuture(); + + final actual = SingleSelectTypeOptionDataParser() + .fromBuffer(editorBloc.state.field.field.typeOptionData); + + expect(actual, equals(typeOptionData)); + }); + + test('update visibility', () async { + expect( + editorBloc.state.field.visibility, + equals(FieldVisibility.AlwaysShown), + ); + + editorBloc.add(const FieldEditorEvent.toggleFieldVisibility()); + await gridResponseFuture(); + + expect( + editorBloc.state.field.visibility, + equals(FieldVisibility.AlwaysHidden), + ); + }); + + test('update wrap cell', () async { + expect( + editorBloc.state.field.wrapCellContent, + equals(true), + ); + + editorBloc.add(const FieldEditorEvent.toggleWrapCellContent()); + await gridResponseFuture(); + + expect( + editorBloc.state.field.wrapCellContent, + equals(false), + ); + }); + + test('insert left and right', () async { + expect( + context.fieldController.fieldInfos.length, + equals(3), + ); + + editorBloc.add(const FieldEditorEvent.insertLeft()); + // TODO(RS): Shouldn't need to wait here!? + await gridResponseFuture(); + editorBloc.add(const FieldEditorEvent.insertRight()); + await gridResponseFuture(); + + expect( + context.fieldController.fieldInfos.length, + equals(5), + ); + }); + }); +} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart deleted file mode 100644 index aec7cc56c3..0000000000 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'package:appflowy/plugins/database/domain/filter_service.dart'; -import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart'; -import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_filter.pbenum.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/text_filter.pb.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../util.dart'; - -void main() { - late AppFlowyGridTest gridTest; - setUpAll(() async { - gridTest = await AppFlowyGridTest.ensureInitialized(); - }); - - test('create a text filter)', () async { - final context = await gridTest.createTestGrid(); - final service = FilterBackendService(viewId: context.gridView.id); - final textField = context.getTextField(); - await service.insertTextFilter( - fieldId: textField.id, - condition: TextFilterConditionPB.TextIsEmpty, - content: "", - ); - await gridResponseFuture(); - - assert(context.fieldController.filters.length == 1); - }); - - test('delete a text filter)', () async { - final context = await gridTest.createTestGrid(); - final service = FilterBackendService(viewId: context.gridView.id); - final textField = context.getTextField(); - await service.insertTextFilter( - fieldId: textField.id, - condition: TextFilterConditionPB.TextIsEmpty, - content: "", - ); - await gridResponseFuture(); - - final filter = context.fieldController.filters.first; - await service.deleteFilter( - filterId: filter.filterId, - ); - await gridResponseFuture(); - - expect(context.fieldController.filters.length, 0); - }); - - test('filter rows with condition: text is empty', () async { - final context = await gridTest.createTestGrid(); - final service = FilterBackendService(viewId: context.gridView.id); - final gridController = DatabaseController( - view: context.gridView, - ); - final gridBloc = GridBloc( - view: context.gridView, - databaseController: gridController, - )..add(const GridEvent.initial()); - await gridResponseFuture(); - - final textField = context.getTextField(); - await service.insertTextFilter( - fieldId: textField.id, - condition: TextFilterConditionPB.TextIsEmpty, - content: "", - ); - await gridResponseFuture(); - - expect(gridBloc.state.rowInfos.length, 3); - }); - - test('filter rows with condition: text is empty(After edit the row)', - () async { - final context = await gridTest.createTestGrid(); - final service = FilterBackendService(viewId: context.gridView.id); - final gridController = DatabaseController( - view: context.gridView, - ); - final gridBloc = GridBloc( - view: context.gridView, - databaseController: gridController, - )..add(const GridEvent.initial()); - await gridResponseFuture(); - - final textField = context.getTextField(); - await service.insertTextFilter( - fieldId: textField.id, - condition: TextFilterConditionPB.TextIsEmpty, - content: "", - ); - await gridResponseFuture(); - - final controller = context.makeTextCellController(0); - await controller.saveCellData("edit text cell content"); - await gridResponseFuture(); - assert(gridBloc.state.rowInfos.length == 2); - - await controller.saveCellData(""); - await gridResponseFuture(); - assert(gridBloc.state.rowInfos.length == 3); - }); - - test('filter rows with condition: text is not empty', () async { - final context = await gridTest.createTestGrid(); - final service = FilterBackendService(viewId: context.gridView.id); - final textField = context.getTextField(); - await gridResponseFuture(); - await service.insertTextFilter( - fieldId: textField.id, - condition: TextFilterConditionPB.TextIsNotEmpty, - content: "", - ); - await gridResponseFuture(); - assert(context.rowInfos.isEmpty); - }); - - test('filter rows with condition: checkbox uncheck', () async { - final context = await gridTest.createTestGrid(); - final checkboxField = context.getCheckboxField(); - final service = FilterBackendService(viewId: context.gridView.id); - final gridController = DatabaseController( - view: context.gridView, - ); - final gridBloc = GridBloc( - view: context.gridView, - databaseController: gridController, - )..add(const GridEvent.initial()); - - await gridResponseFuture(); - await service.insertCheckboxFilter( - fieldId: checkboxField.id, - condition: CheckboxFilterConditionPB.IsUnChecked, - ); - await gridResponseFuture(); - assert(gridBloc.state.rowInfos.length == 3); - }); - - test('filter rows with condition: checkbox check', () async { - final context = await gridTest.createTestGrid(); - final checkboxField = context.getCheckboxField(); - final service = FilterBackendService(viewId: context.gridView.id); - final gridController = DatabaseController( - view: context.gridView, - ); - final gridBloc = GridBloc( - view: context.gridView, - databaseController: gridController, - )..add(const GridEvent.initial()); - - await gridResponseFuture(); - await service.insertCheckboxFilter( - fieldId: checkboxField.id, - condition: CheckboxFilterConditionPB.IsChecked, - ); - await gridResponseFuture(); - assert(gridBloc.state.rowInfos.isEmpty); - }); -} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_menu_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_editor_bloc_test.dart similarity index 64% rename from frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_menu_test.dart rename to frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_editor_bloc_test.dart index 6958ed7b5e..6f139484c4 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_menu_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_editor_bloc_test.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/application/field/filter_entities.dart'; import 'package:appflowy/plugins/database/domain/field_service.dart'; import 'package:appflowy/plugins/database/domain/filter_service.dart'; @@ -9,24 +10,35 @@ import '../util.dart'; void main() { late AppFlowyGridTest gridTest; + setUpAll(() async { gridTest = await AppFlowyGridTest.ensureInitialized(); }); - group('filter editor bloc test:', () { - test('create filter', () async { - final context = await gridTest.createTestGrid(); - final filterBloc = FilterEditorBloc( - viewId: context.gridView.id, + group('filter editor bloc:', () { + late GridTestContext context; + late FilterEditorBloc filterBloc; + + setUp(() async { + context = await gridTest.makeDefaultTestGrid(); + filterBloc = FilterEditorBloc( + viewId: context.view.id, fieldController: context.fieldController, ); - await gridResponseFuture(); + }); + + FieldInfo getFirstFieldByType(FieldType fieldType) { + return context.fieldController.fieldInfos + .firstWhere((field) => field.fieldType == fieldType); + } + + test('create filter', () async { expect(filterBloc.state.filters.length, equals(0)); expect(filterBloc.state.fields.length, equals(3)); // through domain directly - final textField = context.getTextField(); - final service = FilterBackendService(viewId: context.gridView.id); + final textField = getFirstFieldByType(FieldType.RichText); + final service = FilterBackendService(viewId: context.view.id); await service.insertTextFilter( fieldId: textField.id, condition: TextFilterConditionPB.TextIsEmpty, @@ -37,7 +49,7 @@ void main() { expect(filterBloc.state.fields.length, equals(3)); // through bloc event - final selectOptionField = context.getSelectOptionField(); + final selectOptionField = getFirstFieldByType(FieldType.SingleSelect); filterBloc.add(FilterEditorEvent.createFilter(selectOptionField)); await gridResponseFuture(); expect(filterBloc.state.filters.length, equals(2)); @@ -53,15 +65,33 @@ void main() { expect(filterBloc.state.fields.length, equals(3)); }); - test('delete filter', () async { - final context = await gridTest.createTestGrid(); - final filterBloc = FilterEditorBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, + test('change filtering field', () async { + final textField = getFirstFieldByType(FieldType.RichText); + final selectField = getFirstFieldByType(FieldType.Checkbox); + filterBloc.add(FilterEditorEvent.createFilter(textField)); + await gridResponseFuture(); + expect(filterBloc.state.filters.length, equals(1)); + expect(filterBloc.state.fields.length, equals(3)); + expect( + filterBloc.state.filters.first.fieldType, + equals(FieldType.RichText), + ); + + final filter = filterBloc.state.filters.first; + filterBloc.add( + FilterEditorEvent.changeFilteringField(filter.filterId, selectField), ); await gridResponseFuture(); + expect(filterBloc.state.filters.length, equals(1)); + expect( + filterBloc.state.filters.first.fieldType, + equals(FieldType.Checkbox), + ); + expect(filterBloc.state.fields.length, equals(3)); + }); - final textField = context.getTextField(); + test('delete filter', () async { + final textField = getFirstFieldByType(FieldType.RichText); filterBloc.add(FilterEditorEvent.createFilter(textField)); await gridResponseFuture(); expect(filterBloc.state.filters.length, equals(1)); @@ -75,15 +105,8 @@ void main() { }); test('update filter', () async { - final context = await gridTest.createTestGrid(); - final filterBloc = FilterEditorBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, - ); - await gridResponseFuture(); - - final service = FilterBackendService(viewId: context.gridView.id); - final textField = context.getTextField(); + final service = FilterBackendService(viewId: context.view.id); + final textField = getFirstFieldByType(FieldType.RichText); // Create filter await service.insertTextFilter( @@ -96,6 +119,7 @@ void main() { expect(filter.condition, equals(TextFilterConditionPB.TextIsEmpty)); final textFilter = context.fieldController.filters.first; + // Update the existing filter await service.insertTextFilter( fieldId: textField.id, @@ -109,15 +133,8 @@ void main() { expect(filter.content, equals("ABC")); }); - test('update field', () async { - final context = await gridTest.createTestGrid(); - final filterBloc = FilterEditorBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, - ); - await gridResponseFuture(); - - final textField = context.getTextField(); + test('update filtering field\'s name', () async { + final textField = getFirstFieldByType(FieldType.RichText); filterBloc.add(FilterEditorEvent.createFilter(textField)); await gridResponseFuture(); expect(filterBloc.state.filters.length, equals(1)); @@ -127,7 +144,7 @@ void main() { // edit field await FieldBackendService( - viewId: context.gridView.id, + viewId: context.view.id, fieldId: textField.id, ).updateField(name: "New Name"); await gridResponseFuture(); @@ -136,28 +153,40 @@ void main() { }); test('update field type', () async { - final context = await gridTest.createTestGrid(); - final filterBloc = FilterEditorBloc( - viewId: context.gridView.id, - fieldController: context.fieldController, - ); - await gridResponseFuture(); - - final checkboxField = context.getCheckboxField(); + final checkboxField = getFirstFieldByType(FieldType.Checkbox); filterBloc.add(FilterEditorEvent.createFilter(checkboxField)); await gridResponseFuture(); expect(filterBloc.state.filters.length, equals(1)); // edit field await FieldBackendService( - viewId: context.gridView.id, + viewId: context.view.id, fieldId: checkboxField.id, ).updateType(fieldType: FieldType.DateTime); await gridResponseFuture(); - expect(filterBloc.state.filters.length, equals(0)); + // filter is removed + expect(filterBloc.state.filters.length, equals(0)); expect(filterBloc.state.fields.length, equals(3)); expect(filterBloc.state.fields[2].fieldType, FieldType.DateTime); }); + + test('update filter field', () async { + final checkboxField = getFirstFieldByType(FieldType.Checkbox); + filterBloc.add(FilterEditorEvent.createFilter(checkboxField)); + await gridResponseFuture(); + expect(filterBloc.state.filters.length, equals(1)); + + // edit field + await FieldBackendService( + viewId: context.view.id, + fieldId: checkboxField.id, + ).updateField(name: "HERRO"); + await gridResponseFuture(); + + expect(filterBloc.state.filters.length, equals(1)); + expect(filterBloc.state.fields.length, equals(3)); + expect(filterBloc.state.fields[2].name, "HERRO"); + }); }); } diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_entities_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_entities_test.dart new file mode 100644 index 0000000000..fa4031ea90 --- /dev/null +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_entities_test.dart @@ -0,0 +1,623 @@ +import 'dart:typed_data'; + +import 'package:appflowy/plugins/database/application/field/filter_entities.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:fixnum/fixnum.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('parsing filter entities:', () { + FilterPB createFilterPB( + FieldType fieldType, + Uint8List data, + ) { + return FilterPB( + id: "FT", + filterType: FilterType.Data, + data: FilterDataPB( + fieldId: "FD", + fieldType: fieldType, + data: data, + ), + ); + } + + test('text', () async { + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.RichText, + TextFilterPB( + condition: TextFilterConditionPB.TextContains, + content: "c", + ).writeToBuffer(), + ), + ), + equals( + TextFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.RichText, + condition: TextFilterConditionPB.TextContains, + content: "c", + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.RichText, + TextFilterPB( + condition: TextFilterConditionPB.TextContains, + content: "", + ).writeToBuffer(), + ), + ), + equals( + TextFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.RichText, + condition: TextFilterConditionPB.TextContains, + content: "", + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.RichText, + TextFilterPB( + condition: TextFilterConditionPB.TextIsEmpty, + content: "", + ).writeToBuffer(), + ), + ), + equals( + TextFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.RichText, + condition: TextFilterConditionPB.TextIsEmpty, + content: "", + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.RichText, + TextFilterPB( + condition: TextFilterConditionPB.TextIsEmpty, + content: "", + ).writeToBuffer(), + ), + ), + equals( + TextFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.RichText, + condition: TextFilterConditionPB.TextIsEmpty, + content: "c", + ), + ), + ); + }); + + test('number', () async { + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.Number, + NumberFilterPB( + condition: NumberFilterConditionPB.GreaterThan, + content: "", + ).writeToBuffer(), + ), + ), + equals( + NumberFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.Number, + condition: NumberFilterConditionPB.GreaterThan, + content: "", + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.Number, + NumberFilterPB( + condition: NumberFilterConditionPB.GreaterThan, + content: "123", + ).writeToBuffer(), + ), + ), + equals( + NumberFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.Number, + condition: NumberFilterConditionPB.GreaterThan, + content: "123", + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.Number, + NumberFilterPB( + condition: NumberFilterConditionPB.NumberIsEmpty, + content: "", + ).writeToBuffer(), + ), + ), + equals( + NumberFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.Number, + condition: NumberFilterConditionPB.NumberIsEmpty, + content: "", + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.Number, + NumberFilterPB( + condition: NumberFilterConditionPB.NumberIsEmpty, + content: "", + ).writeToBuffer(), + ), + ), + equals( + NumberFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.Number, + condition: NumberFilterConditionPB.NumberIsEmpty, + content: "123", + ), + ), + ); + }); + + test('checkbox', () async { + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.Checkbox, + CheckboxFilterPB( + condition: CheckboxFilterConditionPB.IsChecked, + ).writeToBuffer(), + ), + ), + equals( + const CheckboxFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.Checkbox, + condition: CheckboxFilterConditionPB.IsChecked, + ), + ), + ); + }); + + test('checklist', () async { + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.Checklist, + ChecklistFilterPB( + condition: ChecklistFilterConditionPB.IsComplete, + ).writeToBuffer(), + ), + ), + equals( + const ChecklistFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.Checklist, + condition: ChecklistFilterConditionPB.IsComplete, + ), + ), + ); + }); + + test('single select option', () async { + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.SingleSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: [], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.SingleSelect, + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: const [], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.SingleSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: ['a'], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.SingleSelect, + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: const ['a'], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.SingleSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: ['a', 'b'], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.SingleSelect, + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: const ['a', 'b'], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.SingleSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionIs, + optionIds: ['a', 'b'], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.SingleSelect, + condition: SelectOptionFilterConditionPB.OptionIs, + optionIds: const ['a'], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.SingleSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionIsEmpty, + optionIds: [], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.SingleSelect, + condition: SelectOptionFilterConditionPB.OptionIsEmpty, + optionIds: const [], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.SingleSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionIsEmpty, + optionIds: ['a'], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.SingleSelect, + condition: SelectOptionFilterConditionPB.OptionIsEmpty, + optionIds: const [], + ), + ), + ); + }); + + test('multi select option', () async { + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.MultiSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: [], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.MultiSelect, + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: const [], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.MultiSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: ['a'], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.MultiSelect, + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: const ['a'], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.MultiSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: ['a', 'b'], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.MultiSelect, + condition: SelectOptionFilterConditionPB.OptionContains, + optionIds: const ['a', 'b'], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.MultiSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionIs, + optionIds: ['a', 'b'], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.MultiSelect, + condition: SelectOptionFilterConditionPB.OptionIs, + optionIds: const ['a', 'b'], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.MultiSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionIsEmpty, + optionIds: [], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.MultiSelect, + condition: SelectOptionFilterConditionPB.OptionIsEmpty, + optionIds: const [], + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.MultiSelect, + SelectOptionFilterPB( + condition: SelectOptionFilterConditionPB.OptionIsEmpty, + optionIds: ['a'], + ).writeToBuffer(), + ), + ), + equals( + SelectOptionFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.MultiSelect, + condition: SelectOptionFilterConditionPB.OptionIsEmpty, + optionIds: const [], + ), + ), + ); + }); + + test('date time', () { + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.DateTime, + DateFilterPB( + condition: DateFilterConditionPB.DateStartsOn, + timestamp: Int64(5), + ).writeToBuffer(), + ), + ), + equals( + DateTimeFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.DateTime, + condition: DateFilterConditionPB.DateStartsOn, + timestamp: DateTime.fromMillisecondsSinceEpoch(5 * 1000), + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.DateTime, + DateFilterPB( + condition: DateFilterConditionPB.DateStartsOn, + ).writeToBuffer(), + ), + ), + equals( + DateTimeFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.DateTime, + condition: DateFilterConditionPB.DateStartsOn, + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.DateTime, + DateFilterPB( + condition: DateFilterConditionPB.DateStartsOn, + start: Int64(5), + ).writeToBuffer(), + ), + ), + equals( + DateTimeFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.DateTime, + condition: DateFilterConditionPB.DateStartsOn, + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.DateTime, + DateFilterPB( + condition: DateFilterConditionPB.DateEndsBetween, + start: Int64(5), + ).writeToBuffer(), + ), + ), + equals( + DateTimeFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.DateTime, + condition: DateFilterConditionPB.DateEndsBetween, + start: DateTime.fromMillisecondsSinceEpoch(5 * 1000), + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.DateTime, + DateFilterPB( + condition: DateFilterConditionPB.DateEndIsNotEmpty, + ).writeToBuffer(), + ), + ), + equals( + DateTimeFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.DateTime, + condition: DateFilterConditionPB.DateEndIsNotEmpty, + ), + ), + ); + + expect( + DatabaseFilter.fromPB( + createFilterPB( + FieldType.DateTime, + DateFilterPB( + condition: DateFilterConditionPB.DateEndIsNotEmpty, + start: Int64(5), + end: Int64(5), + timestamp: Int64(5), + ).writeToBuffer(), + ), + ), + equals( + DateTimeFilter( + filterId: "FT", + fieldId: "FD", + fieldType: FieldType.DateTime, + condition: DateFilterConditionPB.DateEndIsNotEmpty, + ), + ), + ); + }); + }); + + // group('write to buffer', () { + // test('text', () {}); + // }); +} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_checkbox_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_checkbox_test.dart deleted file mode 100644 index e6a8979ec0..0000000000 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_checkbox_test.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:appflowy/plugins/database/domain/filter_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_filter.pbenum.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../util.dart'; -import 'filter_util.dart'; - -void main() { - late AppFlowyGridTest gridTest; - setUpAll(() async { - gridTest = await AppFlowyGridTest.ensureInitialized(); - }); - - test('filter rows by checkbox is check condition)', () async { - final context = await createTestFilterGrid(gridTest); - final service = FilterBackendService(viewId: context.gridView.id); - - final controller = context.makeCheckboxCellController(0); - await controller.saveCellData("Yes"); - await gridResponseFuture(); - - // create a new filter - final checkboxField = context.getCheckboxField(); - await service.insertCheckboxFilter( - fieldId: checkboxField.id, - condition: CheckboxFilterConditionPB.IsChecked, - ); - await gridResponseFuture(); - assert( - context.rowInfos.length == 1, - "expect 1 but receive ${context.rowInfos.length}", - ); - }); - - test('filter rows by checkbox is uncheck condition)', () async { - final context = await createTestFilterGrid(gridTest); - final service = FilterBackendService(viewId: context.gridView.id); - - final controller = context.makeCheckboxCellController(0); - await controller.saveCellData("Yes"); - await gridResponseFuture(); - - // create a new filter - final checkboxField = context.getCheckboxField(); - await service.insertCheckboxFilter( - fieldId: checkboxField.id, - condition: CheckboxFilterConditionPB.IsUnChecked, - ); - await gridResponseFuture(); - assert( - context.rowInfos.length == 2, - "expect 2 but receive ${context.rowInfos.length}", - ); - }); -} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart deleted file mode 100644 index 78b4e234c6..0000000000 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'package:appflowy/plugins/database/domain/filter_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/text_filter.pb.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../util.dart'; -import 'filter_util.dart'; - -void main() { - late AppFlowyGridTest gridTest; - setUpAll(() async { - gridTest = await AppFlowyGridTest.ensureInitialized(); - }); - - test('filter rows by text is empty condition)', () async { - final context = await createTestFilterGrid(gridTest); - - final service = FilterBackendService(viewId: context.gridView.id); - final textField = context.getTextField(); - // create a new filter - await service.insertTextFilter( - fieldId: textField.id, - condition: TextFilterConditionPB.TextIsEmpty, - content: "", - ); - await gridResponseFuture(); - assert( - context.fieldController.filters.length == 1, - "expect 1 but receive ${context.fieldController.filters.length}", - ); - assert( - context.rowInfos.length == 1, - "expect 1 but receive ${context.rowInfos.length}", - ); - - // delete the filter - final textFilter = context.fieldController.filters.first; - await service.deleteFilter( - filterId: textFilter.filterId, - ); - await gridResponseFuture(); - assert(context.rowInfos.length == 3); - }); - - test('filter rows by text is not empty condition)', () async { - final context = await createTestFilterGrid(gridTest); - - final service = FilterBackendService(viewId: context.gridView.id); - final textField = context.getTextField(); - // create a new filter - await service.insertTextFilter( - fieldId: textField.id, - condition: TextFilterConditionPB.TextIsNotEmpty, - content: "", - ); - await gridResponseFuture(); - assert( - context.rowInfos.length == 2, - "expect 2 but receive ${context.rowInfos.length}", - ); - - // delete the filter - final textFilter = context.fieldController.filters.first; - await service.deleteFilter( - filterId: textFilter.filterId, - ); - await gridResponseFuture(); - assert(context.rowInfos.length == 3); - }); - - test('filter rows by text is empty or is not empty condition)', () async { - final context = await createTestFilterGrid(gridTest); - - final service = FilterBackendService(viewId: context.gridView.id); - final textField = context.getTextField(); - // create a new filter - await service.insertTextFilter( - fieldId: textField.id, - condition: TextFilterConditionPB.TextIsEmpty, - content: "", - ); - await gridResponseFuture(); - assert( - context.fieldController.filters.length == 1, - "expect 1 but receive ${context.fieldController.filters.length}", - ); - assert( - context.rowInfos.length == 1, - "expect 1 but receive ${context.rowInfos.length}", - ); - - // Update the existing filter - final textFilter = context.fieldController.filters.first; - await service.insertTextFilter( - fieldId: textField.id, - filterId: textFilter.filterId, - condition: TextFilterConditionPB.TextIsNotEmpty, - content: "", - ); - await gridResponseFuture(); - assert(context.rowInfos.length == 2); - - // delete the filter - await service.deleteFilter( - filterId: textFilter.filterId, - ); - await gridResponseFuture(); - assert(context.rowInfos.length == 3); - }); - - test('filter rows by text is condition)', () async { - final context = await createTestFilterGrid(gridTest); - - final service = FilterBackendService(viewId: context.gridView.id); - final textField = context.getTextField(); - // create a new filter - await service.insertTextFilter( - fieldId: textField.id, - condition: TextFilterConditionPB.TextIs, - content: "A", - ); - await gridResponseFuture(); - assert( - context.rowInfos.length == 1, - "expect 1 but receive ${context.rowInfos.length}", - ); - - // Update the existing filter's content from 'A' to 'B' - final textFilter = context.fieldController.filters.first; - await service.insertTextFilter( - fieldId: textField.id, - filterId: textFilter.filterId, - condition: TextFilterConditionPB.TextIs, - content: "B", - ); - await gridResponseFuture(); - assert(context.rowInfos.length == 1); - - // Update the existing filter's content from 'B' to 'b' - await service.insertTextFilter( - fieldId: textField.id, - filterId: textFilter.filterId, - condition: TextFilterConditionPB.TextIs, - content: "b", - ); - await gridResponseFuture(); - assert(context.rowInfos.length == 1); - - // Update the existing filter with content 'C' - await service.insertTextFilter( - fieldId: textField.id, - filterId: textFilter.filterId, - condition: TextFilterConditionPB.TextIs, - content: "C", - ); - await gridResponseFuture(); - assert(context.rowInfos.isEmpty); - - // delete the filter - await service.deleteFilter( - filterId: textFilter.filterId, - ); - await gridResponseFuture(); - assert(context.rowInfos.length == 3); - }); -} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart deleted file mode 100644 index ee73cf44d9..0000000000 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/workspace/application/view/view_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; - -import '../util.dart'; - -Future createTestFilterGrid(AppFlowyGridTest gridTest) async { - final app = await gridTest.unitTest.createWorkspace(); - final context = await ViewBackendService.createView( - parentViewId: app.id, - name: "Filter Grid", - layoutType: ViewLayoutPB.Grid, - openAfterCreate: true, - ).then((result) { - return result.fold( - (view) async { - final context = GridTestContext( - view, - DatabaseController(view: view), - ); - final result = await context.gridController.open(); - - await editCells(context); - result.fold((l) => null, (r) => throw Exception(r)); - return context; - }, - (error) => throw Exception(), - ); - }); - - return context; -} - -Future editCells(GridTestContext context) async { - final controller0 = context.makeTextCellController(0); - final controller1 = context.makeTextCellController(1); - - await controller0.saveCellData('A'); - await gridResponseFuture(); - await controller1.saveCellData('B'); - await gridResponseFuture(); -} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart index e9ae25b96f..8af1a4b7e1 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart @@ -7,6 +7,7 @@ import 'util.dart'; void main() { late AppFlowyGridTest gridTest; + setUpAll(() async { gridTest = await AppFlowyGridTest.ensureInitialized(); }); @@ -15,7 +16,7 @@ void main() { late GridTestContext context; setUp(() async { - context = await gridTest.createTestGrid(); + context = await gridTest.makeDefaultTestGrid(); }); // The initial number of rows is 3 for each grid @@ -23,32 +24,29 @@ void main() { blocTest( "create a row", build: () => GridBloc( - view: context.gridView, - databaseController: DatabaseController(view: context.gridView), + view: context.view, + databaseController: DatabaseController(view: context.view), )..add(const GridEvent.initial()), act: (bloc) => bloc.add(const GridEvent.createRow()), - wait: const Duration(milliseconds: 300), + wait: gridResponseDuration(), verify: (bloc) { - assert(bloc.state.rowInfos.length == 4); + expect(bloc.state.rowInfos.length, equals(4)); }, ); blocTest( "delete the last row", build: () => GridBloc( - view: context.gridView, - databaseController: DatabaseController(view: context.gridView), + view: context.view, + databaseController: DatabaseController(view: context.view), )..add(const GridEvent.initial()), act: (bloc) async { await gridResponseFuture(); bloc.add(GridEvent.deleteRow(bloc.state.rowInfos.last)); }, - wait: const Duration(milliseconds: 300), + wait: gridResponseDuration(), verify: (bloc) { - assert( - bloc.state.rowInfos.length == 2, - "Expected 2, but receive ${bloc.state.rowInfos.length}", - ); + expect(bloc.state.rowInfos.length, equals(2)); }, ); @@ -59,8 +57,8 @@ void main() { blocTest( 'reorder rows', build: () => GridBloc( - view: context.gridView, - databaseController: DatabaseController(view: context.gridView), + view: context.view, + databaseController: DatabaseController(view: context.view), )..add(const GridEvent.initial()), act: (bloc) async { await gridResponseFuture(); @@ -71,10 +69,11 @@ void main() { bloc.add(const GridEvent.moveRow(0, 2)); }, + wait: gridResponseDuration(), verify: (bloc) { - expect(secondId, bloc.state.rowInfos[0].rowId); - expect(thirdId, bloc.state.rowInfos[1].rowId); - expect(firstId, bloc.state.rowInfos[2].rowId); + expect(secondId, equals(bloc.state.rowInfos[0].rowId)); + expect(thirdId, equals(bloc.state.rowInfos[1].rowId)); + expect(firstId, equals(bloc.state.rowInfos[2].rowId)); }, ); }); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/sort/sort_editor_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/sort/sort_editor_bloc_test.dart new file mode 100644 index 0000000000..7a2fcbb119 --- /dev/null +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/sort/sort_editor_bloc_test.dart @@ -0,0 +1,204 @@ +import 'package:appflowy/plugins/database/application/field/field_info.dart'; +import 'package:appflowy/plugins/database/domain/field_service.dart'; +import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../util.dart'; + +void main() { + late AppFlowyGridTest gridTest; + + setUpAll(() async { + gridTest = await AppFlowyGridTest.ensureInitialized(); + }); + + group('sort editor bloc:', () { + late GridTestContext context; + late SortEditorBloc sortBloc; + + setUp(() async { + context = await gridTest.makeDefaultTestGrid(); + sortBloc = SortEditorBloc( + viewId: context.view.id, + fieldController: context.fieldController, + ); + }); + + FieldInfo getFirstFieldByType(FieldType fieldType) { + return context.fieldController.fieldInfos + .firstWhere((field) => field.fieldType == fieldType); + } + + test('create sort', () async { + expect(sortBloc.state.sorts.length, equals(0)); + expect(sortBloc.state.creatableFields.length, equals(3)); + expect(sortBloc.state.allFields.length, equals(3)); + + final selectOptionField = getFirstFieldByType(FieldType.SingleSelect); + sortBloc.add(SortEditorEvent.createSort(fieldId: selectOptionField.id)); + await gridResponseFuture(); + expect(sortBloc.state.sorts.length, 1); + expect(sortBloc.state.sorts.first.fieldId, selectOptionField.id); + expect( + sortBloc.state.sorts.first.condition, + SortConditionPB.Ascending, + ); + expect(sortBloc.state.creatableFields.length, equals(2)); + expect(sortBloc.state.allFields.length, equals(3)); + }); + + test('change sort field', () async { + final selectOptionField = getFirstFieldByType(FieldType.SingleSelect); + sortBloc.add(SortEditorEvent.createSort(fieldId: selectOptionField.id)); + await gridResponseFuture(); + + expect( + sortBloc.state.creatableFields + .map((e) => e.id) + .contains(selectOptionField.id), + false, + ); + + final checkboxField = getFirstFieldByType(FieldType.Checkbox); + sortBloc.add( + SortEditorEvent.editSort( + sortId: sortBloc.state.sorts.first.sortId, + fieldId: checkboxField.id, + ), + ); + await gridResponseFuture(); + + expect(sortBloc.state.creatableFields.length, equals(2)); + expect( + sortBloc.state.creatableFields + .map((e) => e.id) + .contains(checkboxField.id), + false, + ); + }); + + test('update sort direction', () async { + final selectOptionField = getFirstFieldByType(FieldType.SingleSelect); + sortBloc.add(SortEditorEvent.createSort(fieldId: selectOptionField.id)); + await gridResponseFuture(); + + expect( + sortBloc.state.sorts.first.condition, + SortConditionPB.Ascending, + ); + + sortBloc.add( + SortEditorEvent.editSort( + sortId: sortBloc.state.sorts.first.sortId, + condition: SortConditionPB.Descending, + ), + ); + await gridResponseFuture(); + + expect( + sortBloc.state.sorts.first.condition, + SortConditionPB.Descending, + ); + }); + + test('reorder sorts', () async { + final selectOptionField = getFirstFieldByType(FieldType.SingleSelect); + final checkboxField = getFirstFieldByType(FieldType.Checkbox); + sortBloc + ..add(SortEditorEvent.createSort(fieldId: selectOptionField.id)) + ..add(SortEditorEvent.createSort(fieldId: checkboxField.id)); + await gridResponseFuture(); + + expect(sortBloc.state.sorts[0].fieldId, selectOptionField.id); + expect(sortBloc.state.sorts[1].fieldId, checkboxField.id); + expect(sortBloc.state.creatableFields.length, equals(1)); + expect(sortBloc.state.allFields.length, equals(3)); + + sortBloc.add( + const SortEditorEvent.reorderSort(0, 2), + ); + await gridResponseFuture(); + + expect(sortBloc.state.sorts[0].fieldId, checkboxField.id); + expect(sortBloc.state.sorts[1].fieldId, selectOptionField.id); + expect(sortBloc.state.creatableFields.length, equals(1)); + expect(sortBloc.state.allFields.length, equals(3)); + }); + + test('delete sort', () async { + final selectOptionField = getFirstFieldByType(FieldType.SingleSelect); + sortBloc.add(SortEditorEvent.createSort(fieldId: selectOptionField.id)); + await gridResponseFuture(); + + expect(sortBloc.state.sorts.length, 1); + + sortBloc.add( + SortEditorEvent.deleteSort(sortBloc.state.sorts.first.sortId), + ); + await gridResponseFuture(); + + expect(sortBloc.state.sorts.length, 0); + expect(sortBloc.state.creatableFields.length, equals(3)); + expect(sortBloc.state.allFields.length, equals(3)); + }); + + test('delete all sorts', () async { + final selectOptionField = getFirstFieldByType(FieldType.SingleSelect); + final checkboxField = getFirstFieldByType(FieldType.Checkbox); + sortBloc + ..add(SortEditorEvent.createSort(fieldId: selectOptionField.id)) + ..add(SortEditorEvent.createSort(fieldId: checkboxField.id)); + await gridResponseFuture(); + + expect(sortBloc.state.sorts.length, 2); + + sortBloc.add(const SortEditorEvent.deleteAllSorts()); + await gridResponseFuture(); + + expect(sortBloc.state.sorts.length, 0); + expect(sortBloc.state.creatableFields.length, equals(3)); + expect(sortBloc.state.allFields.length, equals(3)); + }); + + test('update sort field', () async { + final selectOptionField = getFirstFieldByType(FieldType.SingleSelect); + sortBloc.add(SortEditorEvent.createSort(fieldId: selectOptionField.id)); + await gridResponseFuture(); + + expect(sortBloc.state.sorts.length, equals(1)); + + // edit field + await FieldBackendService( + viewId: context.view.id, + fieldId: selectOptionField.id, + ).updateField(name: "HERRO"); + await gridResponseFuture(); + + expect(sortBloc.state.sorts.length, equals(1)); + expect(sortBloc.state.allFields[1].name, "HERRO"); + + expect(sortBloc.state.creatableFields.length, equals(2)); + expect(sortBloc.state.allFields.length, equals(3)); + }); + + test('delete sorting field', () async { + final selectOptionField = getFirstFieldByType(FieldType.SingleSelect); + sortBloc.add(SortEditorEvent.createSort(fieldId: selectOptionField.id)); + await gridResponseFuture(); + + expect(sortBloc.state.sorts.length, equals(1)); + + // edit field + await FieldBackendService( + viewId: context.view.id, + fieldId: selectOptionField.id, + ).delete(); + await gridResponseFuture(); + + expect(sortBloc.state.sorts.length, equals(0)); + expect(sortBloc.state.creatableFields.length, equals(2)); + expect(sortBloc.state.allFields.length, equals(2)); + }); + }); +} diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart index a158ecfed5..8629461172 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart @@ -1,91 +1,52 @@ +import 'dart:convert'; + import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/field/field_controller.dart'; import 'package:appflowy/plugins/database/application/field/field_editor_bloc.dart'; -import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/domain/field_service.dart'; import 'package:appflowy/plugins/database/application/row/row_cache.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/plugins/database/application/row/row_service.dart'; +import 'package:appflowy/workspace/application/settings/share/import_service.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:flutter/services.dart'; import '../../util.dart'; -class GridTestContext { - GridTestContext(this.gridView, this.gridController); +const v020GridFileName = "v020.afdb"; +const v069GridFileName = "v069.afdb"; - final ViewPB gridView; - final DatabaseController gridController; +class GridTestContext { + GridTestContext(this.view, this.databaseController); + + final ViewPB view; + final DatabaseController databaseController; List get rowInfos { - return gridController.rowCache.rowInfos; + return databaseController.rowCache.rowInfos; } - List get fieldInfos => fieldController.fieldInfos; - - FieldController get fieldController { - return gridController.fieldController; - } + FieldController get fieldController => databaseController.fieldController; Future createField(FieldType fieldType) async { final editorBloc = - await createFieldEditor(databaseController: gridController); + await createFieldEditor(databaseController: databaseController); await gridResponseFuture(); editorBloc.add(FieldEditorEvent.switchFieldType(fieldType)); await gridResponseFuture(); - return Future(() => editorBloc); + return editorBloc; } - FieldInfo getSelectOptionField() { - final fieldInfo = fieldInfos - .firstWhere((element) => element.fieldType == FieldType.SingleSelect); - return fieldInfo; - } - - FieldInfo getTextField() { - final fieldInfo = fieldInfos - .firstWhere((element) => element.fieldType == FieldType.RichText); - return fieldInfo; - } - - FieldInfo getCheckboxField() { - final fieldInfo = fieldInfos - .firstWhere((element) => element.fieldType == FieldType.Checkbox); - return fieldInfo; - } - - SelectOptionCellController makeSelectOptionCellController( - FieldType fieldType, - int rowIndex, - ) { - assert( - fieldType == FieldType.SingleSelect || fieldType == FieldType.MultiSelect, - ); - final field = - fieldInfos.firstWhere((fieldInfo) => fieldInfo.fieldType == fieldType); + CellController makeGridCellController(int fieldIndex, int rowIndex) { return makeCellController( - gridController, - CellContext(fieldId: field.id, rowId: rowInfos[rowIndex].rowId), - ).as(); - } - - TextCellController makeTextCellController(int rowIndex) { - final field = fieldInfos - .firstWhere((element) => element.fieldType == FieldType.RichText); - return makeCellController( - gridController, - CellContext(fieldId: field.id, rowId: rowInfos[rowIndex].rowId), - ).as(); - } - - CheckboxCellController makeCheckboxCellController(int rowIndex) { - final field = fieldInfos - .firstWhere((element) => element.fieldType == FieldType.Checkbox); - return makeCellController( - gridController, - CellContext(fieldId: field.id, rowId: rowInfos[rowIndex].rowId), + databaseController, + CellContext( + fieldId: fieldController.fieldInfos[fieldIndex].id, + rowId: rowInfos[rowIndex].rowId, + ), ).as(); } } @@ -121,65 +82,74 @@ class AppFlowyGridTest { return AppFlowyGridTest(unitTest: inner); } - Future createTestGrid() async { - final app = await unitTest.createWorkspace(); + Future makeDefaultTestGrid() async { + final workspace = await unitTest.createWorkspace(); final context = await ViewBackendService.createView( - parentViewId: app.id, + parentViewId: workspace.id, name: "Test Grid", layoutType: ViewLayoutPB.Grid, openAfterCreate: true, - ).then((result) { - return result.fold( - (view) async { - final context = GridTestContext( - view, - DatabaseController(view: view), - ); - final result = await context.gridController.open(); - result.fold((l) => null, (r) => throw Exception(r)); - return context; - }, - (error) { - throw Exception(); - }, - ); - }); + ).fold( + (view) async { + final databaseController = DatabaseController(view: view); + await databaseController + .open() + .fold((l) => null, (r) => throw Exception(r)); + return GridTestContext( + view, + databaseController, + ); + }, + (error) => throw Exception(), + ); + + return context; + } + + Future makeTestGridFromImportedData( + String fileName, + ) async { + final workspace = await unitTest.createWorkspace(); + + // Don't use the p.join to build the path that used in loadString. It + // is not working on windows. + final data = await rootBundle + .loadString("assets/test/workspaces/database/$fileName"); + + final context = await ImportBackendService.importPages( + workspace.id, + [ + ImportValuePayloadPB() + ..name = fileName + ..data = utf8.encode(data) + ..viewLayout = ViewLayoutPB.Grid + ..importType = ImportTypePB.RawDatabase, + ], + ).fold( + (views) async { + final view = views.items.first; + final databaseController = DatabaseController(view: view); + await databaseController + .open() + .fold((l) => null, (r) => throw Exception(r)); + return GridTestContext( + view, + databaseController, + ); + }, + (err) => throw Exception(), + ); return context; } } -/// Create a new Grid for cell test -class AppFlowyGridCellTest { - AppFlowyGridCellTest({required this.gridTest}); - - late GridTestContext context; - final AppFlowyGridTest gridTest; - - static Future ensureInitialized() async { - final gridTest = await AppFlowyGridTest.ensureInitialized(); - return AppFlowyGridCellTest(gridTest: gridTest); - } - - Future createTestGrid() async { - context = await gridTest.createTestGrid(); - } - - Future createTestRow() async { - await RowBackendService.createRow(viewId: context.gridView.id); - } - - SelectOptionCellController makeSelectOptionCellController( - FieldType fieldType, - int rowIndex, - ) => - context.makeSelectOptionCellController(fieldType, rowIndex); +Future gridResponseFuture({int milliseconds = 300}) { + return Future.delayed( + gridResponseDuration(milliseconds: milliseconds), + ); } -Future gridResponseFuture({int milliseconds = 400}) { - return Future.delayed(gridResponseDuration(milliseconds: milliseconds)); -} - -Duration gridResponseDuration({int milliseconds = 400}) { +Duration gridResponseDuration({int milliseconds = 300}) { return Duration(milliseconds: milliseconds); } diff --git a/frontend/appflowy_flutter/test/util.dart b/frontend/appflowy_flutter/test/util.dart index 3b0c310a96..c4f2a21c64 100644 --- a/frontend/appflowy_flutter/test/util.dart +++ b/frontend/appflowy_flutter/test/util.dart @@ -24,7 +24,7 @@ class AppFlowyUnitTest { _pathProviderInitialized(); await FlowyRunner.run( - AppFlowyApplicationUniTest(), + AppFlowyApplicationUnitTest(), IntegrationMode.unitTest, ); @@ -93,7 +93,7 @@ void _pathProviderInitialized() { }); } -class AppFlowyApplicationUniTest implements EntryPoint { +class AppFlowyApplicationUnitTest implements EntryPoint { @override Widget create(LaunchConfiguration config) { return const SizedBox.shrink();