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:
Richard Shiue
2024-03-14 14:27:57 +08:00
committed by GitHub
parent 1a34366a3f
commit 5a837a9482
57 changed files with 1338 additions and 1266 deletions

View File

@ -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(),

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,