mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support checkbox filter (#1492)
* feat: support checkbox filter * fix: unit test Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
parent
d6cbbf3c2f
commit
c47f755155
@ -184,6 +184,13 @@
|
||||
"isNotEmpty": "is not empty"
|
||||
}
|
||||
},
|
||||
"checkboxFilter": {
|
||||
"isChecked": "Checked",
|
||||
"isUnchecked": "Unchecked",
|
||||
"choicechipPrefix": {
|
||||
"is": "is"
|
||||
}
|
||||
},
|
||||
"field": {
|
||||
"hide": "Hide",
|
||||
"insertLeft": "Insert Left",
|
||||
|
@ -527,9 +527,15 @@ class FieldInfo {
|
||||
bool get canCreateFilter {
|
||||
if (hasFilter) return false;
|
||||
|
||||
if (_field.fieldType != FieldType.RichText) return false;
|
||||
|
||||
return true;
|
||||
switch (_field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
// case FieldType.MultiSelect:
|
||||
case FieldType.RichText:
|
||||
// case FieldType.SingleSelect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
FieldInfo({required FieldPB field}) : _field = field;
|
||||
|
@ -0,0 +1,99 @@
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'filter_listener.dart';
|
||||
import 'filter_service.dart';
|
||||
|
||||
part 'checkbox_filter_editor_bloc.freezed.dart';
|
||||
|
||||
class CheckboxFilterEditorBloc
|
||||
extends Bloc<CheckboxFilterEditorEvent, CheckboxFilterEditorState> {
|
||||
final FilterInfo filterInfo;
|
||||
final FilterFFIService _ffiService;
|
||||
final FilterListener _listener;
|
||||
|
||||
CheckboxFilterEditorBloc({required this.filterInfo})
|
||||
: _ffiService = FilterFFIService(viewId: filterInfo.viewId),
|
||||
_listener = FilterListener(
|
||||
viewId: filterInfo.viewId,
|
||||
filterId: filterInfo.filter.id,
|
||||
),
|
||||
super(CheckboxFilterEditorState.initial(filterInfo)) {
|
||||
on<CheckboxFilterEditorEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
},
|
||||
updateCondition: (CheckboxFilterCondition condition) {
|
||||
_ffiService.insertCheckboxFilter(
|
||||
filterId: filterInfo.filter.id,
|
||||
fieldId: filterInfo.field.id,
|
||||
condition: condition,
|
||||
);
|
||||
},
|
||||
delete: () {
|
||||
_ffiService.deleteFilter(
|
||||
fieldId: filterInfo.field.id,
|
||||
filterId: filterInfo.filter.id,
|
||||
fieldType: filterInfo.field.fieldType,
|
||||
);
|
||||
},
|
||||
didReceiveFilter: (FilterPB filter) {
|
||||
final filterInfo = state.filterInfo.copyWith(filter: filter);
|
||||
final checkboxFilter = filterInfo.checkboxFilter()!;
|
||||
emit(state.copyWith(
|
||||
filterInfo: filterInfo,
|
||||
filter: checkboxFilter,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_listener.start(
|
||||
onDeleted: () {
|
||||
if (!isClosed) add(const CheckboxFilterEditorEvent.delete());
|
||||
},
|
||||
onUpdated: (filter) {
|
||||
if (!isClosed) add(CheckboxFilterEditorEvent.didReceiveFilter(filter));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _listener.stop();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CheckboxFilterEditorEvent with _$CheckboxFilterEditorEvent {
|
||||
const factory CheckboxFilterEditorEvent.initial() = _Initial;
|
||||
const factory CheckboxFilterEditorEvent.didReceiveFilter(FilterPB filter) =
|
||||
_DidReceiveFilter;
|
||||
const factory CheckboxFilterEditorEvent.updateCondition(
|
||||
CheckboxFilterCondition condition) = _UpdateCondition;
|
||||
const factory CheckboxFilterEditorEvent.delete() = _Delete;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CheckboxFilterEditorState with _$CheckboxFilterEditorState {
|
||||
const factory CheckboxFilterEditorState({
|
||||
required FilterInfo filterInfo,
|
||||
required CheckboxFilterPB filter,
|
||||
}) = _GridFilterState;
|
||||
|
||||
factory CheckboxFilterEditorState.initial(FilterInfo filterInfo) {
|
||||
return CheckboxFilterEditorState(
|
||||
filterInfo: filterInfo,
|
||||
filter: filterInfo.checkboxFilter()!,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,15 +1,210 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/filter/checkbox_filter_editor_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/condition_button.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/disclosure_button.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'choicechip.dart';
|
||||
|
||||
class CheckboxFilterChoicechip extends StatelessWidget {
|
||||
class CheckboxFilterChoicechip extends StatefulWidget {
|
||||
final FilterInfo filterInfo;
|
||||
const CheckboxFilterChoicechip({required this.filterInfo, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<CheckboxFilterChoicechip> createState() =>
|
||||
_CheckboxFilterChoicechipState();
|
||||
}
|
||||
|
||||
class _CheckboxFilterChoicechipState extends State<CheckboxFilterChoicechip> {
|
||||
late CheckboxFilterEditorBloc bloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
bloc = CheckboxFilterEditorBloc(filterInfo: widget.filterInfo)
|
||||
..add(const CheckboxFilterEditorEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
bloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChoiceChipButton(filterInfo: filterInfo);
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: BlocBuilder<CheckboxFilterEditorBloc, CheckboxFilterEditorState>(
|
||||
builder: (blocContext, state) {
|
||||
return AppFlowyPopover(
|
||||
controller: PopoverController(),
|
||||
constraints: BoxConstraints.loose(const Size(200, 76)),
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
popupBuilder: (BuildContext context) {
|
||||
return CheckboxFilterEditor(bloc: bloc);
|
||||
},
|
||||
child: ChoiceChipButton(
|
||||
filterInfo: widget.filterInfo,
|
||||
filterDesc: _makeFilterDesc(state),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _makeFilterDesc(CheckboxFilterEditorState state) {
|
||||
final prefix = LocaleKeys.grid_checkboxFilter_choicechipPrefix_is.tr();
|
||||
return "$prefix ${state.filter.condition.filterName}";
|
||||
}
|
||||
}
|
||||
|
||||
class CheckboxFilterEditor extends StatefulWidget {
|
||||
final CheckboxFilterEditorBloc bloc;
|
||||
const CheckboxFilterEditor({required this.bloc, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<CheckboxFilterEditor> createState() => _CheckboxFilterEditorState();
|
||||
}
|
||||
|
||||
class _CheckboxFilterEditorState extends State<CheckboxFilterEditor> {
|
||||
final popoverMutex = PopoverMutex();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: widget.bloc,
|
||||
child: BlocBuilder<CheckboxFilterEditorBloc, CheckboxFilterEditorState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [
|
||||
_buildFilterPannel(context, state),
|
||||
];
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
child: IntrinsicHeight(child: Column(children: children)),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterPannel(
|
||||
BuildContext context, CheckboxFilterEditorState state) {
|
||||
return SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
children: [
|
||||
FlowyText(state.filterInfo.field.name),
|
||||
const HSpace(4),
|
||||
CheckboxFilterConditionList(
|
||||
filterInfo: state.filterInfo,
|
||||
popoverMutex: popoverMutex,
|
||||
onCondition: (condition) {
|
||||
context
|
||||
.read<CheckboxFilterEditorBloc>()
|
||||
.add(CheckboxFilterEditorEvent.updateCondition(condition));
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
DisclosureButton(
|
||||
popoverMutex: popoverMutex,
|
||||
onAction: (action) {
|
||||
switch (action) {
|
||||
case FilterDisclosureAction.delete:
|
||||
context
|
||||
.read<CheckboxFilterEditorBloc>()
|
||||
.add(const CheckboxFilterEditorEvent.delete());
|
||||
break;
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CheckboxFilterConditionList extends StatelessWidget {
|
||||
final FilterInfo filterInfo;
|
||||
final PopoverMutex popoverMutex;
|
||||
final Function(CheckboxFilterCondition) onCondition;
|
||||
const CheckboxFilterConditionList({
|
||||
required this.filterInfo,
|
||||
required this.popoverMutex,
|
||||
required this.onCondition,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final checkboxFilter = filterInfo.checkboxFilter()!;
|
||||
return PopoverActionList<ConditionWrapper>(
|
||||
asBarrier: true,
|
||||
mutex: popoverMutex,
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
actions: CheckboxFilterCondition.values
|
||||
.map(
|
||||
(action) => ConditionWrapper(
|
||||
action,
|
||||
checkboxFilter.condition == action,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
buildChild: (controller) {
|
||||
return ConditionButton(
|
||||
conditionName: checkboxFilter.condition.filterName,
|
||||
onTap: () => controller.show(),
|
||||
);
|
||||
},
|
||||
onSelected: (action, controller) async {
|
||||
onCondition(action.inner);
|
||||
controller.close();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConditionWrapper extends ActionCell {
|
||||
final CheckboxFilterCondition inner;
|
||||
final bool isSelected;
|
||||
|
||||
ConditionWrapper(this.inner, this.isSelected);
|
||||
|
||||
@override
|
||||
Widget? rightIcon(Color iconColor) {
|
||||
if (isSelected) {
|
||||
return svgWidget("grid/checkmark");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => inner.filterName;
|
||||
}
|
||||
|
||||
extension TextFilterConditionExtension on CheckboxFilterCondition {
|
||||
String get filterName {
|
||||
switch (this) {
|
||||
case CheckboxFilterCondition.IsChecked:
|
||||
return LocaleKeys.grid_checkboxFilter_isChecked.tr();
|
||||
case CheckboxFilterCondition.IsUnChecked:
|
||||
return LocaleKeys.grid_checkboxFilter_isUnchecked.tr();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
|
||||
@ -32,4 +33,11 @@ class FilterInfo {
|
||||
}
|
||||
return TextFilterPB.fromBuffer(filter.data);
|
||||
}
|
||||
|
||||
CheckboxFilterPB? checkboxFilter() {
|
||||
if (filter.fieldType != FieldType.Checkbox) {
|
||||
return null;
|
||||
}
|
||||
return CheckboxFilterPB.fromBuffer(filter.data);
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ void main() {
|
||||
viewId: context.gridView.id, fieldController: context.fieldController)
|
||||
..add(const GridFilterMenuEvent.initial());
|
||||
await gridResponseFuture();
|
||||
assert(menuBloc.state.creatableFields.length == 1);
|
||||
assert(menuBloc.state.creatableFields.length == 2);
|
||||
|
||||
final service = FilterFFIService(viewId: context.gridView.id);
|
||||
final textField = context.textFieldContext();
|
||||
@ -26,7 +26,7 @@ void main() {
|
||||
condition: TextFilterCondition.TextIsEmpty,
|
||||
content: "");
|
||||
await gridResponseFuture();
|
||||
assert(menuBloc.state.creatableFields.isEmpty);
|
||||
assert(menuBloc.state.creatableFields.length == 1);
|
||||
});
|
||||
|
||||
test('test filter menu after update existing text filter)', () async {
|
||||
|
@ -11,6 +11,58 @@ void main() {
|
||||
gridTest = await AppFlowyGridTest.ensureInitialized();
|
||||
});
|
||||
|
||||
test('filter rows by text is empty condition)', () async {
|
||||
final context = await createTestFilterGrid(gridTest);
|
||||
|
||||
final service = FilterFFIService(viewId: context.gridView.id);
|
||||
final textField = context.textFieldContext();
|
||||
// create a new filter
|
||||
await service.insertTextFilter(
|
||||
fieldId: textField.id,
|
||||
condition: TextFilterCondition.TextIsEmpty,
|
||||
content: "");
|
||||
await gridResponseFuture();
|
||||
assert(context.fieldController.filterInfos.length == 1,
|
||||
"expect 1 but receive ${context.fieldController.filterInfos.length}");
|
||||
assert(context.rowInfos.length == 1,
|
||||
"expect 1 but receive ${context.rowInfos.length}");
|
||||
|
||||
// delete the filter
|
||||
final textFilter = context.fieldController.filterInfos.first;
|
||||
await service.deleteFilter(
|
||||
fieldId: textField.id,
|
||||
filterId: textFilter.filter.id,
|
||||
fieldType: textField.fieldType,
|
||||
);
|
||||
await gridResponseFuture();
|
||||
assert(context.rowInfos.length == 3);
|
||||
});
|
||||
|
||||
test('filter rows by text is not empty condition)', () async {
|
||||
final context = await createTestFilterGrid(gridTest);
|
||||
|
||||
final service = FilterFFIService(viewId: context.gridView.id);
|
||||
final textField = context.textFieldContext();
|
||||
// create a new filter
|
||||
await service.insertTextFilter(
|
||||
fieldId: textField.id,
|
||||
condition: TextFilterCondition.TextIsNotEmpty,
|
||||
content: "");
|
||||
await gridResponseFuture();
|
||||
assert(context.rowInfos.length == 2,
|
||||
"expect 2 but receive ${context.rowInfos.length}");
|
||||
|
||||
// delete the filter
|
||||
final textFilter = context.fieldController.filterInfos.first;
|
||||
await service.deleteFilter(
|
||||
fieldId: textField.id,
|
||||
filterId: textFilter.filter.id,
|
||||
fieldType: textField.fieldType,
|
||||
);
|
||||
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);
|
||||
|
||||
|
@ -95,15 +95,13 @@ pub(crate) struct FilterResult {
|
||||
|
||||
impl FilterResult {
|
||||
pub(crate) fn is_visible(&self) -> bool {
|
||||
if self.visible_by_filter_id.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut is_visible = true;
|
||||
for visible in self.visible_by_filter_id.values() {
|
||||
if visible == &false {
|
||||
return false;
|
||||
if !is_visible {
|
||||
break;
|
||||
}
|
||||
is_visible = *visible;
|
||||
}
|
||||
true
|
||||
is_visible
|
||||
}
|
||||
}
|
||||
|
@ -309,11 +309,13 @@ fn filter_row(
|
||||
let filter_result = result_by_row_id
|
||||
.entry(row_rev.id.clone())
|
||||
.or_insert_with(FilterResult::default);
|
||||
let old_is_visible = filter_result.is_visible();
|
||||
|
||||
// Iterate each cell of the row to check its visibility
|
||||
for (field_id, field_rev) in field_rev_by_field_id {
|
||||
let filter_type = FilterType::from(field_rev);
|
||||
if !filter_map.has_filter(&filter_type) {
|
||||
filter_result.visible_by_filter_id.remove(&filter_type);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -321,16 +323,16 @@ fn filter_row(
|
||||
// if the visibility of the cell_rew is changed, which means the visibility of the
|
||||
// row is changed too.
|
||||
if let Some(is_visible) = filter_cell(&filter_type, field_rev, filter_map, cell_rev) {
|
||||
let old_is_visible = filter_result.visible_by_filter_id.get(&filter_type).cloned();
|
||||
filter_result.visible_by_filter_id.insert(filter_type, is_visible);
|
||||
return if old_is_visible != Some(is_visible) {
|
||||
Some((row_rev.id.clone(), is_visible))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
Some((row_rev.id.clone(), true))
|
||||
|
||||
let is_visible = filter_result.is_visible();
|
||||
return if old_is_visible != is_visible {
|
||||
Some((row_rev.id.clone(), is_visible))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
// Returns None if there is no change in this cell after applying the filter
|
||||
|
@ -5,12 +5,14 @@ use flowy_grid::entities::CheckboxFilterCondition;
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checkbox_is_check_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
// The initial number of unchecked is 3
|
||||
// The initial number of checked is 2
|
||||
let scripts = vec![
|
||||
CreateCheckboxFilter {
|
||||
condition: CheckboxFilterCondition::IsChecked,
|
||||
},
|
||||
AssertFilterChanged {
|
||||
visible_row_len: 2,
|
||||
visible_row_len: 0,
|
||||
hide_row_len: 3,
|
||||
},
|
||||
];
|
||||
|
@ -13,7 +13,7 @@ async fn grid_filter_text_is_empty_test() {
|
||||
},
|
||||
AssertFilterCount { count: 1 },
|
||||
AssertFilterChanged {
|
||||
visible_row_len: 1,
|
||||
visible_row_len: 0,
|
||||
hide_row_len: 4,
|
||||
},
|
||||
];
|
||||
@ -23,6 +23,7 @@ async fn grid_filter_text_is_empty_test() {
|
||||
#[tokio::test]
|
||||
async fn grid_filter_text_is_not_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
// Only one row's text of the initial rows is ""
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterCondition::TextIsNotEmpty,
|
||||
@ -30,23 +31,38 @@ async fn grid_filter_text_is_not_empty_test() {
|
||||
},
|
||||
AssertFilterCount { count: 1 },
|
||||
AssertFilterChanged {
|
||||
visible_row_len: 4,
|
||||
visible_row_len: 0,
|
||||
hide_row_len: 1,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
|
||||
let filter = test.grid_filters().await.pop().unwrap();
|
||||
let field_rev = test.get_field_rev(FieldType::RichText).clone();
|
||||
test.run_scripts(vec![
|
||||
DeleteFilter {
|
||||
filter_id: filter.id,
|
||||
filter_type: FilterType::from(&field_rev),
|
||||
},
|
||||
// AssertFilterChanged {
|
||||
// visible_row_len: 1,
|
||||
// hide_row_len: 0,
|
||||
// },
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_is_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
// Only one row's text of the initial rows is "A"
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterCondition::Is,
|
||||
content: "A".to_string(),
|
||||
},
|
||||
AssertFilterChanged {
|
||||
visible_row_len: 1,
|
||||
visible_row_len: 0,
|
||||
hide_row_len: 4,
|
||||
},
|
||||
];
|
||||
@ -62,7 +78,7 @@ async fn grid_filter_contain_text_test() {
|
||||
content: "A".to_string(),
|
||||
},
|
||||
AssertFilterChanged {
|
||||
visible_row_len: 3,
|
||||
visible_row_len: 0,
|
||||
hide_row_len: 2,
|
||||
},
|
||||
];
|
||||
@ -78,7 +94,7 @@ async fn grid_filter_contain_text_test2() {
|
||||
content: "A".to_string(),
|
||||
},
|
||||
AssertFilterChanged {
|
||||
visible_row_len: 3,
|
||||
visible_row_len: 0,
|
||||
hide_row_len: 2,
|
||||
},
|
||||
UpdateTextCell {
|
||||
@ -96,18 +112,20 @@ async fn grid_filter_contain_text_test2() {
|
||||
#[tokio::test]
|
||||
async fn grid_filter_does_not_contain_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
// None of the initial rows contains the text "AB"
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterCondition::DoesNotContain,
|
||||
content: "AB".to_string(),
|
||||
},
|
||||
AssertFilterChanged {
|
||||
visible_row_len: 5,
|
||||
visible_row_len: 0,
|
||||
hide_row_len: 0,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_start_with_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
@ -117,7 +135,7 @@ async fn grid_filter_start_with_text_test() {
|
||||
content: "A".to_string(),
|
||||
},
|
||||
AssertFilterChanged {
|
||||
visible_row_len: 2,
|
||||
visible_row_len: 0,
|
||||
hide_row_len: 3,
|
||||
},
|
||||
];
|
||||
@ -201,7 +219,7 @@ async fn grid_filter_update_empty_text_cell_test() {
|
||||
},
|
||||
AssertFilterCount { count: 1 },
|
||||
AssertFilterChanged {
|
||||
visible_row_len: 1,
|
||||
visible_row_len: 0,
|
||||
hide_row_len: 4,
|
||||
},
|
||||
UpdateTextCell {
|
||||
|
Loading…
Reference in New Issue
Block a user