mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: number and select filter logic (#4828)
* fix: number and select option filter bugs * chore: rename filter condition enum and variants
This commit is contained in:
@ -8,38 +8,41 @@ use crate::services::{field::SelectOptionIds, filter::ParseFilterData};
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct SelectOptionFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: SelectOptionConditionPB,
|
||||
pub condition: SelectOptionFilterConditionPB,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub option_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
#[derive(Default)]
|
||||
pub enum SelectOptionConditionPB {
|
||||
pub enum SelectOptionFilterConditionPB {
|
||||
#[default]
|
||||
OptionIs = 0,
|
||||
OptionIsNot = 1,
|
||||
OptionIsEmpty = 2,
|
||||
OptionIsNotEmpty = 3,
|
||||
OptionContains = 2,
|
||||
OptionDoesNotContain = 3,
|
||||
OptionIsEmpty = 4,
|
||||
OptionIsNotEmpty = 5,
|
||||
}
|
||||
|
||||
impl std::convert::From<SelectOptionConditionPB> for u32 {
|
||||
fn from(value: SelectOptionConditionPB) -> Self {
|
||||
impl From<SelectOptionFilterConditionPB> for u32 {
|
||||
fn from(value: SelectOptionFilterConditionPB) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for SelectOptionConditionPB {
|
||||
impl TryFrom<u8> for SelectOptionFilterConditionPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(SelectOptionConditionPB::OptionIs),
|
||||
1 => Ok(SelectOptionConditionPB::OptionIsNot),
|
||||
2 => Ok(SelectOptionConditionPB::OptionIsEmpty),
|
||||
3 => Ok(SelectOptionConditionPB::OptionIsNotEmpty),
|
||||
0 => Ok(SelectOptionFilterConditionPB::OptionIs),
|
||||
1 => Ok(SelectOptionFilterConditionPB::OptionIsNot),
|
||||
2 => Ok(SelectOptionFilterConditionPB::OptionContains),
|
||||
3 => Ok(SelectOptionFilterConditionPB::OptionDoesNotContain),
|
||||
4 => Ok(SelectOptionFilterConditionPB::OptionIsEmpty),
|
||||
5 => Ok(SelectOptionFilterConditionPB::OptionIsNotEmpty),
|
||||
_ => Err(ErrorCode::InvalidParams),
|
||||
}
|
||||
}
|
||||
@ -47,8 +50,8 @@ impl std::convert::TryFrom<u8> for SelectOptionConditionPB {
|
||||
impl ParseFilterData for SelectOptionFilterPB {
|
||||
fn parse(condition: u8, content: String) -> Self {
|
||||
Self {
|
||||
condition: SelectOptionConditionPB::try_from(condition)
|
||||
.unwrap_or(SelectOptionConditionPB::OptionIs),
|
||||
condition: SelectOptionFilterConditionPB::try_from(condition)
|
||||
.unwrap_or(SelectOptionFilterConditionPB::OptionIs),
|
||||
option_ids: SelectOptionIds::from_str(&content)
|
||||
.unwrap_or_default()
|
||||
.into_inner(),
|
||||
|
@ -12,17 +12,16 @@ pub struct TextFilterPB {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
#[derive(Default)]
|
||||
pub enum TextFilterConditionPB {
|
||||
#[default]
|
||||
Is = 0,
|
||||
IsNot = 1,
|
||||
Contains = 2,
|
||||
DoesNotContain = 3,
|
||||
StartsWith = 4,
|
||||
EndsWith = 5,
|
||||
TextIs = 0,
|
||||
TextIsNot = 1,
|
||||
TextContains = 2,
|
||||
TextDoesNotContain = 3,
|
||||
TextStartsWith = 4,
|
||||
TextEndsWith = 5,
|
||||
TextIsEmpty = 6,
|
||||
TextIsNotEmpty = 7,
|
||||
}
|
||||
@ -38,12 +37,12 @@ impl std::convert::TryFrom<u8> for TextFilterConditionPB {
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(TextFilterConditionPB::Is),
|
||||
1 => Ok(TextFilterConditionPB::IsNot),
|
||||
2 => Ok(TextFilterConditionPB::Contains),
|
||||
3 => Ok(TextFilterConditionPB::DoesNotContain),
|
||||
4 => Ok(TextFilterConditionPB::StartsWith),
|
||||
5 => Ok(TextFilterConditionPB::EndsWith),
|
||||
0 => Ok(TextFilterConditionPB::TextIs),
|
||||
1 => Ok(TextFilterConditionPB::TextIsNot),
|
||||
2 => Ok(TextFilterConditionPB::TextContains),
|
||||
3 => Ok(TextFilterConditionPB::TextDoesNotContain),
|
||||
4 => Ok(TextFilterConditionPB::TextStartsWith),
|
||||
5 => Ok(TextFilterConditionPB::TextEndsWith),
|
||||
6 => Ok(TextFilterConditionPB::TextIsEmpty),
|
||||
7 => Ok(TextFilterConditionPB::TextIsNotEmpty),
|
||||
_ => Err(ErrorCode::InvalidParams),
|
||||
@ -54,7 +53,8 @@ impl std::convert::TryFrom<u8> for TextFilterConditionPB {
|
||||
impl ParseFilterData for TextFilterPB {
|
||||
fn parse(condition: u8, content: String) -> Self {
|
||||
Self {
|
||||
condition: TextFilterConditionPB::try_from(condition).unwrap_or(TextFilterConditionPB::Is),
|
||||
condition: TextFilterConditionPB::try_from(condition)
|
||||
.unwrap_or(TextFilterConditionPB::TextIs),
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,26 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
use crate::entities::{NumberFilterConditionPB, NumberFilterPB};
|
||||
use crate::services::field::NumberCellFormat;
|
||||
use rust_decimal::prelude::Zero;
|
||||
use rust_decimal::Decimal;
|
||||
use std::str::FromStr;
|
||||
|
||||
impl NumberFilterPB {
|
||||
pub fn is_visible(&self, cell_data: &NumberCellFormat) -> Option<bool> {
|
||||
let expected_decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero());
|
||||
let expected_decimal = || Decimal::from_str(&self.content).ok();
|
||||
|
||||
let strategy = match self.condition {
|
||||
NumberFilterConditionPB::Equal => NumberFilterStrategy::Equal(expected_decimal),
|
||||
NumberFilterConditionPB::NotEqual => NumberFilterStrategy::NotEqual(expected_decimal),
|
||||
NumberFilterConditionPB::GreaterThan => NumberFilterStrategy::GreaterThan(expected_decimal),
|
||||
NumberFilterConditionPB::LessThan => NumberFilterStrategy::LessThan(expected_decimal),
|
||||
NumberFilterConditionPB::Equal => NumberFilterStrategy::Equal(expected_decimal()?),
|
||||
NumberFilterConditionPB::NotEqual => NumberFilterStrategy::NotEqual(expected_decimal()?),
|
||||
NumberFilterConditionPB::GreaterThan => {
|
||||
NumberFilterStrategy::GreaterThan(expected_decimal()?)
|
||||
},
|
||||
NumberFilterConditionPB::LessThan => NumberFilterStrategy::LessThan(expected_decimal()?),
|
||||
NumberFilterConditionPB::GreaterThanOrEqualTo => {
|
||||
NumberFilterStrategy::GreaterThanOrEqualTo(expected_decimal)
|
||||
NumberFilterStrategy::GreaterThanOrEqualTo(expected_decimal()?)
|
||||
},
|
||||
NumberFilterConditionPB::LessThanOrEqualTo => {
|
||||
NumberFilterStrategy::LessThanOrEqualTo(expected_decimal)
|
||||
NumberFilterStrategy::LessThanOrEqualTo(expected_decimal()?)
|
||||
},
|
||||
NumberFilterConditionPB::NumberIsEmpty => NumberFilterStrategy::Empty,
|
||||
NumberFilterConditionPB::NumberIsNotEmpty => NumberFilterStrategy::NotEmpty,
|
||||
|
@ -128,7 +128,7 @@ impl TypeOptionCellDataFilter for MultiSelectTypeOption {
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
let selected_options = self.get_selected_options(cell_data.clone()).select_options;
|
||||
filter.is_visible(&selected_options, FieldType::MultiSelect)
|
||||
filter.is_visible(&selected_options).unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,106 +1,110 @@
|
||||
#![allow(clippy::needless_collect)]
|
||||
|
||||
use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
|
||||
use crate::entities::{SelectOptionFilterConditionPB, SelectOptionFilterPB};
|
||||
use crate::services::field::SelectOption;
|
||||
|
||||
impl SelectOptionFilterPB {
|
||||
pub fn is_visible(&self, selected_options: &[SelectOption], field_type: FieldType) -> bool {
|
||||
let selected_option_ids: Vec<&String> =
|
||||
selected_options.iter().map(|option| &option.id).collect();
|
||||
match self.condition {
|
||||
SelectOptionConditionPB::OptionIs => match field_type {
|
||||
FieldType::SingleSelect => {
|
||||
if self.option_ids.is_empty() {
|
||||
return true;
|
||||
}
|
||||
pub fn is_visible(&self, selected_options: &[SelectOption]) -> Option<bool> {
|
||||
let selected_option_ids = selected_options
|
||||
.iter()
|
||||
.map(|option| &option.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if selected_options.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let get_non_empty_expected_options =
|
||||
|| (!self.option_ids.is_empty()).then(|| self.option_ids.clone());
|
||||
|
||||
let required_options = self
|
||||
.option_ids
|
||||
.iter()
|
||||
.filter(|id| selected_option_ids.contains(id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
!required_options.is_empty()
|
||||
},
|
||||
FieldType::MultiSelect => {
|
||||
if self.option_ids.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let required_options = self
|
||||
.option_ids
|
||||
.iter()
|
||||
.filter(|id| selected_option_ids.contains(id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
!required_options.is_empty()
|
||||
},
|
||||
_ => false,
|
||||
let strategy = match self.condition {
|
||||
SelectOptionFilterConditionPB::OptionIs => {
|
||||
SelectOptionFilterStrategy::Is(get_non_empty_expected_options()?)
|
||||
},
|
||||
SelectOptionConditionPB::OptionIsNot => match field_type {
|
||||
FieldType::SingleSelect => {
|
||||
if self.option_ids.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if selected_options.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let required_options = self
|
||||
.option_ids
|
||||
.iter()
|
||||
.filter(|id| selected_option_ids.contains(id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
required_options.is_empty()
|
||||
},
|
||||
FieldType::MultiSelect => {
|
||||
let required_options = self
|
||||
.option_ids
|
||||
.iter()
|
||||
.filter(|id| selected_option_ids.contains(id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
required_options.is_empty()
|
||||
},
|
||||
_ => false,
|
||||
SelectOptionFilterConditionPB::OptionIsNot => {
|
||||
SelectOptionFilterStrategy::IsNot(get_non_empty_expected_options()?)
|
||||
},
|
||||
SelectOptionConditionPB::OptionIsEmpty => selected_option_ids.is_empty(),
|
||||
SelectOptionConditionPB::OptionIsNotEmpty => !selected_option_ids.is_empty(),
|
||||
SelectOptionFilterConditionPB::OptionContains => {
|
||||
SelectOptionFilterStrategy::Contains(get_non_empty_expected_options()?)
|
||||
},
|
||||
SelectOptionFilterConditionPB::OptionDoesNotContain => {
|
||||
SelectOptionFilterStrategy::DoesNotContain(get_non_empty_expected_options()?)
|
||||
},
|
||||
SelectOptionFilterConditionPB::OptionIsEmpty => SelectOptionFilterStrategy::IsEmpty,
|
||||
SelectOptionFilterConditionPB::OptionIsNotEmpty => SelectOptionFilterStrategy::IsNotEmpty,
|
||||
};
|
||||
|
||||
Some(strategy.filter(&selected_option_ids))
|
||||
}
|
||||
}
|
||||
|
||||
enum SelectOptionFilterStrategy {
|
||||
Is(Vec<String>),
|
||||
IsNot(Vec<String>),
|
||||
Contains(Vec<String>),
|
||||
DoesNotContain(Vec<String>),
|
||||
IsEmpty,
|
||||
IsNotEmpty,
|
||||
}
|
||||
|
||||
impl SelectOptionFilterStrategy {
|
||||
fn filter(self, selected_option_ids: &[&String]) -> bool {
|
||||
match self {
|
||||
SelectOptionFilterStrategy::Is(option_ids) => {
|
||||
if selected_option_ids.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
selected_option_ids.len() == option_ids.len()
|
||||
&& selected_option_ids.iter().all(|id| option_ids.contains(id))
|
||||
},
|
||||
SelectOptionFilterStrategy::IsNot(option_ids) => {
|
||||
if selected_option_ids.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
selected_option_ids.len() != option_ids.len()
|
||||
|| !selected_option_ids.iter().all(|id| option_ids.contains(id))
|
||||
},
|
||||
SelectOptionFilterStrategy::Contains(option_ids) => {
|
||||
if selected_option_ids.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let required_options = option_ids
|
||||
.into_iter()
|
||||
.filter(|id| selected_option_ids.contains(&id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
!required_options.is_empty()
|
||||
},
|
||||
SelectOptionFilterStrategy::DoesNotContain(option_ids) => {
|
||||
if selected_option_ids.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let required_options = option_ids
|
||||
.into_iter()
|
||||
.filter(|id| selected_option_ids.contains(&id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
required_options.is_empty()
|
||||
},
|
||||
SelectOptionFilterStrategy::IsEmpty => selected_option_ids.is_empty(),
|
||||
SelectOptionFilterStrategy::IsNotEmpty => !selected_option_ids.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::all)]
|
||||
use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
|
||||
use crate::entities::{SelectOptionFilterConditionPB, SelectOptionFilterPB};
|
||||
use crate::services::field::SelectOption;
|
||||
|
||||
#[test]
|
||||
fn select_option_filter_is_empty_test() {
|
||||
let option = SelectOption::new("A");
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIsEmpty,
|
||||
condition: SelectOptionFilterConditionPB::OptionIsEmpty,
|
||||
option_ids: vec![],
|
||||
};
|
||||
|
||||
assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), true);
|
||||
assert_eq!(
|
||||
filter.is_visible(&vec![option.clone()], FieldType::SingleSelect),
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), true);
|
||||
assert_eq!(
|
||||
filter.is_visible(&vec![option], FieldType::MultiSelect),
|
||||
false,
|
||||
);
|
||||
assert_eq!(filter.is_visible(&[]), Some(true));
|
||||
assert_eq!(filter.is_visible(&[option.clone()]), Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -108,157 +112,227 @@ mod tests {
|
||||
let option_1 = SelectOption::new("A");
|
||||
let option_2 = SelectOption::new("B");
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIsNotEmpty,
|
||||
condition: SelectOptionFilterConditionPB::OptionIsNotEmpty,
|
||||
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
filter.is_visible(&vec![option_1.clone()], FieldType::SingleSelect),
|
||||
true
|
||||
);
|
||||
assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), false,);
|
||||
|
||||
assert_eq!(
|
||||
filter.is_visible(&vec![option_1.clone()], FieldType::MultiSelect),
|
||||
true
|
||||
);
|
||||
assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), false,);
|
||||
assert_eq!(filter.is_visible(&[]), Some(false));
|
||||
assert_eq!(filter.is_visible(&[option_1.clone()]), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_select_option_filter_is_not_test() {
|
||||
fn select_option_filter_is_test() {
|
||||
let option_1 = SelectOption::new("A");
|
||||
let option_2 = SelectOption::new("B");
|
||||
let option_3 = SelectOption::new("C");
|
||||
|
||||
// no expected options
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIsNot,
|
||||
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
|
||||
condition: SelectOptionFilterConditionPB::OptionIs,
|
||||
option_ids: vec![],
|
||||
};
|
||||
|
||||
for (options, is_visible) in vec![
|
||||
(vec![option_2.clone()], false),
|
||||
(vec![option_1.clone()], false),
|
||||
(vec![option_3.clone()], true),
|
||||
(vec![option_1.clone(), option_2.clone()], false),
|
||||
for (options, is_visible) in [
|
||||
(vec![], None),
|
||||
(vec![option_1.clone()], None),
|
||||
(vec![option_1.clone(), option_2.clone()], None),
|
||||
] {
|
||||
assert_eq!(
|
||||
filter.is_visible(&options, FieldType::SingleSelect),
|
||||
is_visible
|
||||
);
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_select_option_filter_is_test() {
|
||||
let option_1 = SelectOption::new("A");
|
||||
let option_2 = SelectOption::new("B");
|
||||
let option_3 = SelectOption::new("c");
|
||||
|
||||
// one expected option
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
condition: SelectOptionFilterConditionPB::OptionIs,
|
||||
option_ids: vec![option_1.id.clone()],
|
||||
};
|
||||
for (options, is_visible) in vec![
|
||||
(vec![option_1.clone()], true),
|
||||
(vec![option_2.clone()], false),
|
||||
(vec![option_3.clone()], false),
|
||||
(vec![option_1.clone(), option_2.clone()], true),
|
||||
for (options, is_visible) in [
|
||||
(vec![], Some(false)),
|
||||
(vec![option_1.clone()], Some(true)),
|
||||
(vec![option_2.clone()], Some(false)),
|
||||
(vec![option_3.clone()], Some(false)),
|
||||
(vec![option_1.clone(), option_2.clone()], Some(false)),
|
||||
] {
|
||||
assert_eq!(
|
||||
filter.is_visible(&options, FieldType::SingleSelect),
|
||||
is_visible
|
||||
);
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_select_option_filter_is_test2() {
|
||||
let option_1 = SelectOption::new("A");
|
||||
let option_2 = SelectOption::new("B");
|
||||
|
||||
// multiple expected options
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
option_ids: vec![],
|
||||
};
|
||||
for (options, is_visible) in vec![
|
||||
(vec![option_1.clone()], true),
|
||||
(vec![option_2.clone()], true),
|
||||
(vec![option_1.clone(), option_2.clone()], true),
|
||||
] {
|
||||
assert_eq!(
|
||||
filter.is_visible(&options, FieldType::SingleSelect),
|
||||
is_visible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_select_option_filter_not_contains_test() {
|
||||
let option_1 = SelectOption::new("A");
|
||||
let option_2 = SelectOption::new("B");
|
||||
let option_3 = SelectOption::new("C");
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIsNot,
|
||||
condition: SelectOptionFilterConditionPB::OptionIs,
|
||||
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
|
||||
};
|
||||
|
||||
for (options, is_visible) in vec![
|
||||
(vec![option_1.clone(), option_2.clone()], false),
|
||||
(vec![option_1.clone()], false),
|
||||
(vec![option_2.clone()], false),
|
||||
(vec![option_3.clone()], true),
|
||||
for (options, is_visible) in [
|
||||
(vec![], Some(false)),
|
||||
(vec![option_1.clone()], Some(false)),
|
||||
(vec![option_1.clone(), option_2.clone()], Some(true)),
|
||||
(
|
||||
vec![option_1.clone(), option_2.clone(), option_3.clone()],
|
||||
false,
|
||||
Some(false),
|
||||
),
|
||||
(vec![], true),
|
||||
] {
|
||||
assert_eq!(
|
||||
filter.is_visible(&options, FieldType::MultiSelect),
|
||||
is_visible
|
||||
);
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_select_option_filter_contains_test() {
|
||||
fn select_option_filter_is_not_test() {
|
||||
let option_1 = SelectOption::new("A");
|
||||
let option_2 = SelectOption::new("B");
|
||||
let option_3 = SelectOption::new("C");
|
||||
|
||||
// no expected options
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
condition: SelectOptionFilterConditionPB::OptionIsNot,
|
||||
option_ids: vec![],
|
||||
};
|
||||
for (options, is_visible) in [
|
||||
(vec![], None),
|
||||
(vec![option_1.clone()], None),
|
||||
(vec![option_1.clone(), option_2.clone()], None),
|
||||
] {
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
|
||||
// one expected option
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionFilterConditionPB::OptionIsNot,
|
||||
option_ids: vec![option_1.id.clone()],
|
||||
};
|
||||
for (options, is_visible) in [
|
||||
(vec![], Some(true)),
|
||||
(vec![option_1.clone()], Some(false)),
|
||||
(vec![option_2.clone()], Some(true)),
|
||||
(vec![option_3.clone()], Some(true)),
|
||||
(vec![option_1.clone(), option_2.clone()], Some(true)),
|
||||
] {
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
|
||||
// multiple expected options
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionFilterConditionPB::OptionIsNot,
|
||||
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
|
||||
};
|
||||
for (options, is_visible) in vec![
|
||||
for (options, is_visible) in [
|
||||
(vec![], Some(true)),
|
||||
(vec![option_1.clone()], Some(true)),
|
||||
(vec![option_1.clone(), option_2.clone()], Some(false)),
|
||||
(
|
||||
vec![option_1.clone(), option_2.clone(), option_3.clone()],
|
||||
true,
|
||||
Some(true),
|
||||
),
|
||||
(vec![option_2.clone(), option_1.clone()], true),
|
||||
(vec![option_2.clone()], true),
|
||||
(vec![option_1.clone(), option_3.clone()], true),
|
||||
(vec![option_3.clone()], false),
|
||||
] {
|
||||
assert_eq!(
|
||||
filter.is_visible(&options, FieldType::MultiSelect),
|
||||
is_visible
|
||||
);
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_select_option_filter_contains_test2() {
|
||||
fn select_option_filter_contains_test() {
|
||||
let option_1 = SelectOption::new("A");
|
||||
let option_2 = SelectOption::new("B");
|
||||
let option_3 = SelectOption::new("C");
|
||||
let option_4 = SelectOption::new("D");
|
||||
|
||||
// no expected options
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
condition: SelectOptionFilterConditionPB::OptionContains,
|
||||
option_ids: vec![],
|
||||
};
|
||||
for (options, is_visible) in vec![(vec![option_1.clone()], true), (vec![], true)] {
|
||||
assert_eq!(
|
||||
filter.is_visible(&options, FieldType::MultiSelect),
|
||||
is_visible
|
||||
);
|
||||
for (options, is_visible) in [
|
||||
(vec![], None),
|
||||
(vec![option_1.clone()], None),
|
||||
(vec![option_1.clone(), option_2.clone()], None),
|
||||
] {
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
|
||||
// one expected option
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionFilterConditionPB::OptionContains,
|
||||
option_ids: vec![option_1.id.clone()],
|
||||
};
|
||||
for (options, is_visible) in [
|
||||
(vec![], Some(false)),
|
||||
(vec![option_1.clone()], Some(true)),
|
||||
(vec![option_2.clone()], Some(false)),
|
||||
(vec![option_1.clone(), option_2.clone()], Some(true)),
|
||||
(vec![option_3.clone(), option_4.clone()], Some(false)),
|
||||
] {
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
|
||||
// multiple expected options
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionFilterConditionPB::OptionContains,
|
||||
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
|
||||
};
|
||||
for (options, is_visible) in [
|
||||
(vec![], Some(false)),
|
||||
(vec![option_1.clone()], Some(true)),
|
||||
(vec![option_3.clone()], Some(false)),
|
||||
(vec![option_1.clone(), option_2.clone()], Some(true)),
|
||||
(vec![option_1.clone(), option_3.clone()], Some(true)),
|
||||
(vec![option_3.clone(), option_4.clone()], Some(false)),
|
||||
(
|
||||
vec![option_1.clone(), option_3.clone(), option_4.clone()],
|
||||
Some(true),
|
||||
),
|
||||
] {
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_option_filter_does_not_contain_test() {
|
||||
let option_1 = SelectOption::new("A");
|
||||
let option_2 = SelectOption::new("B");
|
||||
let option_3 = SelectOption::new("C");
|
||||
let option_4 = SelectOption::new("D");
|
||||
|
||||
// no expected options
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionFilterConditionPB::OptionDoesNotContain,
|
||||
option_ids: vec![],
|
||||
};
|
||||
for (options, is_visible) in [
|
||||
(vec![], None),
|
||||
(vec![option_1.clone()], None),
|
||||
(vec![option_1.clone(), option_2.clone()], None),
|
||||
] {
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
|
||||
// one expected option
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionFilterConditionPB::OptionDoesNotContain,
|
||||
option_ids: vec![option_1.id.clone()],
|
||||
};
|
||||
for (options, is_visible) in [
|
||||
(vec![], Some(true)),
|
||||
(vec![option_1.clone()], Some(false)),
|
||||
(vec![option_2.clone()], Some(true)),
|
||||
(vec![option_1.clone(), option_2.clone()], Some(false)),
|
||||
(vec![option_3.clone(), option_4.clone()], Some(true)),
|
||||
] {
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
|
||||
// multiple expected options
|
||||
let filter = SelectOptionFilterPB {
|
||||
condition: SelectOptionFilterConditionPB::OptionDoesNotContain,
|
||||
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
|
||||
};
|
||||
for (options, is_visible) in [
|
||||
(vec![], Some(true)),
|
||||
(vec![option_1.clone()], Some(false)),
|
||||
(vec![option_3.clone()], Some(true)),
|
||||
(vec![option_1.clone(), option_2.clone()], Some(false)),
|
||||
(vec![option_1.clone(), option_3.clone()], Some(false)),
|
||||
(vec![option_3.clone(), option_4.clone()], Some(true)),
|
||||
(
|
||||
vec![option_1.clone(), option_3.clone(), option_4.clone()],
|
||||
Some(false),
|
||||
),
|
||||
] {
|
||||
assert_eq!(filter.is_visible(&options), is_visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ impl TypeOptionCellDataFilter for SingleSelectTypeOption {
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
let selected_options = self.get_selected_options(cell_data.clone()).select_options;
|
||||
filter.is_visible(&selected_options, FieldType::SingleSelect)
|
||||
filter.is_visible(&selected_options).unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,12 @@ impl TextFilterPB {
|
||||
let cell_data = cell_data.as_ref().to_lowercase();
|
||||
let content = &self.content.to_lowercase();
|
||||
match self.condition {
|
||||
TextFilterConditionPB::Is => &cell_data == content,
|
||||
TextFilterConditionPB::IsNot => &cell_data != content,
|
||||
TextFilterConditionPB::Contains => cell_data.contains(content),
|
||||
TextFilterConditionPB::DoesNotContain => !cell_data.contains(content),
|
||||
TextFilterConditionPB::StartsWith => cell_data.starts_with(content),
|
||||
TextFilterConditionPB::EndsWith => cell_data.ends_with(content),
|
||||
TextFilterConditionPB::TextIs => &cell_data == content,
|
||||
TextFilterConditionPB::TextIsNot => &cell_data != content,
|
||||
TextFilterConditionPB::TextContains => cell_data.contains(content),
|
||||
TextFilterConditionPB::TextDoesNotContain => !cell_data.contains(content),
|
||||
TextFilterConditionPB::TextStartsWith => cell_data.starts_with(content),
|
||||
TextFilterConditionPB::TextEndsWith => cell_data.ends_with(content),
|
||||
TextFilterConditionPB::TextIsEmpty => cell_data.is_empty(),
|
||||
TextFilterConditionPB::TextIsNotEmpty => !cell_data.is_empty(),
|
||||
}
|
||||
@ -25,7 +25,7 @@ mod tests {
|
||||
#[test]
|
||||
fn text_filter_equal_test() {
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterConditionPB::Is,
|
||||
condition: TextFilterConditionPB::TextIs,
|
||||
content: "appflowy".to_owned(),
|
||||
};
|
||||
|
||||
@ -37,7 +37,7 @@ mod tests {
|
||||
#[test]
|
||||
fn text_filter_start_with_test() {
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterConditionPB::StartsWith,
|
||||
condition: TextFilterConditionPB::TextStartsWith,
|
||||
content: "appflowy".to_owned(),
|
||||
};
|
||||
|
||||
@ -49,7 +49,7 @@ mod tests {
|
||||
#[test]
|
||||
fn text_filter_end_with_test() {
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterConditionPB::EndsWith,
|
||||
condition: TextFilterConditionPB::TextEndsWith,
|
||||
content: "appflowy".to_owned(),
|
||||
};
|
||||
|
||||
@ -70,7 +70,7 @@ mod tests {
|
||||
#[test]
|
||||
fn text_filter_contain_test() {
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterConditionPB::Contains,
|
||||
condition: TextFilterConditionPB::TextContains,
|
||||
content: "appflowy".to_owned(),
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use flowy_database2::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
|
||||
use flowy_database2::entities::{FieldType, SelectOptionFilterConditionPB, SelectOptionFilterPB};
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::database::filter_test::script::FilterScript::*;
|
||||
@ -12,7 +12,7 @@ async fn grid_filter_multi_select_is_empty_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::MultiSelect,
|
||||
data: BoxAny::new(SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIsEmpty,
|
||||
condition: SelectOptionFilterConditionPB::OptionIsEmpty,
|
||||
option_ids: vec![],
|
||||
}),
|
||||
changed: None,
|
||||
@ -30,7 +30,7 @@ async fn grid_filter_multi_select_is_not_empty_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::MultiSelect,
|
||||
data: BoxAny::new(SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIsNotEmpty,
|
||||
condition: SelectOptionFilterConditionPB::OptionIsNotEmpty,
|
||||
option_ids: vec![],
|
||||
}),
|
||||
changed: None,
|
||||
@ -50,12 +50,12 @@ async fn grid_filter_multi_select_is_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::MultiSelect,
|
||||
data: BoxAny::new(SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
condition: SelectOptionFilterConditionPB::OptionIs,
|
||||
option_ids: vec![options.remove(0).id, options.remove(0).id],
|
||||
}),
|
||||
changed: None,
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 5 },
|
||||
AssertNumberOfVisibleRows { expected: 1 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
@ -70,12 +70,12 @@ async fn grid_filter_multi_select_is_test2() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::MultiSelect,
|
||||
data: BoxAny::new(SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
condition: SelectOptionFilterConditionPB::OptionIs,
|
||||
option_ids: vec![options.remove(1).id],
|
||||
}),
|
||||
changed: None,
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 4 },
|
||||
AssertNumberOfVisibleRows { expected: 1 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
@ -90,7 +90,7 @@ async fn grid_filter_single_select_is_empty_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::SingleSelect,
|
||||
data: BoxAny::new(SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIsEmpty,
|
||||
condition: SelectOptionFilterConditionPB::OptionIsEmpty,
|
||||
option_ids: vec![],
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
@ -115,7 +115,7 @@ async fn grid_filter_single_select_is_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::SingleSelect,
|
||||
data: BoxAny::new(SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
condition: SelectOptionFilterConditionPB::OptionIs,
|
||||
option_ids: vec![options.remove(0).id],
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
@ -142,7 +142,7 @@ async fn grid_filter_single_select_is_test2() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::SingleSelect,
|
||||
data: BoxAny::new(SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
condition: SelectOptionFilterConditionPB::OptionIs,
|
||||
option_ids: vec![option.id.clone()],
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
@ -169,3 +169,43 @@ async fn grid_filter_single_select_is_test2() {
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_multi_select_contains_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let field = test.get_first_field(FieldType::MultiSelect);
|
||||
let mut options = test.get_multi_select_type_option(&field.id);
|
||||
let scripts = vec![
|
||||
CreateDataFilter {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::MultiSelect,
|
||||
data: BoxAny::new(SelectOptionFilterPB {
|
||||
condition: SelectOptionFilterConditionPB::OptionContains,
|
||||
option_ids: vec![options.remove(0).id, options.remove(0).id],
|
||||
}),
|
||||
changed: None,
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 5 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_multi_select_contains_test2() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let field = test.get_first_field(FieldType::MultiSelect);
|
||||
let mut options = test.get_multi_select_type_option(&field.id);
|
||||
let scripts = vec![
|
||||
CreateDataFilter {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::MultiSelect,
|
||||
data: BoxAny::new(SelectOptionFilterPB {
|
||||
condition: SelectOptionFilterConditionPB::OptionContains,
|
||||
option_ids: vec![options.remove(1).id],
|
||||
}),
|
||||
changed: None,
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 4 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ async fn grid_filter_is_text_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::RichText,
|
||||
data: BoxAny::new(TextFilterPB {
|
||||
condition: TextFilterConditionPB::Is,
|
||||
condition: TextFilterConditionPB::TextIs,
|
||||
content: "A".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
@ -88,7 +88,7 @@ async fn grid_filter_contain_text_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::RichText,
|
||||
data: BoxAny::new(TextFilterPB {
|
||||
condition: TextFilterConditionPB::Contains,
|
||||
condition: TextFilterConditionPB::TextContains,
|
||||
content: "A".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
@ -109,7 +109,7 @@ async fn grid_filter_contain_text_test2() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::RichText,
|
||||
data: BoxAny::new(TextFilterPB {
|
||||
condition: TextFilterConditionPB::Contains,
|
||||
condition: TextFilterConditionPB::TextContains,
|
||||
content: "A".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
@ -137,7 +137,7 @@ async fn grid_filter_does_not_contain_text_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::RichText,
|
||||
data: BoxAny::new(TextFilterPB {
|
||||
condition: TextFilterConditionPB::DoesNotContain,
|
||||
condition: TextFilterConditionPB::TextDoesNotContain,
|
||||
content: "AB".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
@ -155,7 +155,7 @@ async fn grid_filter_start_with_text_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::RichText,
|
||||
data: BoxAny::new(TextFilterPB {
|
||||
condition: TextFilterConditionPB::StartsWith,
|
||||
condition: TextFilterConditionPB::TextStartsWith,
|
||||
content: "A".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
@ -174,7 +174,7 @@ async fn grid_filter_ends_with_text_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::RichText,
|
||||
data: BoxAny::new(TextFilterPB {
|
||||
condition: TextFilterConditionPB::EndsWith,
|
||||
condition: TextFilterConditionPB::TextEndsWith,
|
||||
content: "A".to_string(),
|
||||
}),
|
||||
changed: None,
|
||||
@ -192,7 +192,7 @@ async fn grid_update_text_filter_test() {
|
||||
parent_filter_id: None,
|
||||
field_type: FieldType::RichText,
|
||||
data: BoxAny::new(TextFilterPB {
|
||||
condition: TextFilterConditionPB::EndsWith,
|
||||
condition: TextFilterConditionPB::TextEndsWith,
|
||||
content: "A".to_string(),
|
||||
}),
|
||||
changed: Some(FilterRowChanged {
|
||||
@ -210,7 +210,7 @@ async fn grid_update_text_filter_test() {
|
||||
let scripts = vec![
|
||||
UpdateTextFilter {
|
||||
filter,
|
||||
condition: TextFilterConditionPB::Is,
|
||||
condition: TextFilterConditionPB::TextIs,
|
||||
content: "A".to_string(),
|
||||
changed: Some(FilterRowChanged {
|
||||
showing_num_of_rows: 0,
|
||||
|
Reference in New Issue
Block a user