mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: add checkbox & select option filter tests
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
use crate::services::field::select_option::SelectOptionIds;
|
#![allow(clippy::needless_collect)]
|
||||||
|
use crate::services::field::select_option::{SelectOptionIds, SelectedSelectOptions};
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
use flowy_error::ErrorCode;
|
use flowy_error::ErrorCode;
|
||||||
use flowy_grid_data_model::revision::GridFilterRevision;
|
use flowy_grid_data_model::revision::GridFilterRevision;
|
||||||
@ -9,14 +10,35 @@ pub struct GridSelectOptionFilter {
|
|||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
pub condition: SelectOptionCondition,
|
pub condition: SelectOptionCondition,
|
||||||
|
|
||||||
#[pb(index = 2, one_of)]
|
#[pb(index = 2)]
|
||||||
pub content: Option<String>,
|
pub option_ids: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GridSelectOptionFilter {
|
impl GridSelectOptionFilter {
|
||||||
pub fn apply(&self, _ids: &SelectOptionIds) -> bool {
|
pub fn apply(&self, selected_options: &SelectedSelectOptions) -> bool {
|
||||||
|
let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect();
|
||||||
|
match self.condition {
|
||||||
|
SelectOptionCondition::OptionIs => {
|
||||||
|
let required_options = self
|
||||||
|
.option_ids
|
||||||
|
.iter()
|
||||||
|
.filter(|id| selected_option_ids.contains(id))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect
|
||||||
|
required_options.is_empty()
|
||||||
|
}
|
||||||
|
SelectOptionCondition::OptionIsNot => {
|
||||||
|
for option_id in selected_option_ids {
|
||||||
|
if self.option_ids.contains(option_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(),
|
||||||
|
SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||||
@ -56,9 +78,42 @@ impl std::convert::TryFrom<u8> for SelectOptionCondition {
|
|||||||
|
|
||||||
impl std::convert::From<Arc<GridFilterRevision>> for GridSelectOptionFilter {
|
impl std::convert::From<Arc<GridFilterRevision>> for GridSelectOptionFilter {
|
||||||
fn from(rev: Arc<GridFilterRevision>) -> Self {
|
fn from(rev: Arc<GridFilterRevision>) -> Self {
|
||||||
|
let ids = SelectOptionIds::from(rev.content.clone());
|
||||||
GridSelectOptionFilter {
|
GridSelectOptionFilter {
|
||||||
condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
|
condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
|
||||||
content: rev.content.clone(),
|
option_ids: ids.into_inner(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#![allow(clippy::all)]
|
||||||
|
use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
|
||||||
|
use crate::services::field::select_option::{SelectOption, SelectedSelectOptions};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_option_filter_is_test() {
|
||||||
|
let option_1 = SelectOption::new("A");
|
||||||
|
let option_2 = SelectOption::new("B");
|
||||||
|
|
||||||
|
let filter_1 = GridSelectOptionFilter {
|
||||||
|
condition: SelectOptionCondition::OptionIs,
|
||||||
|
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
filter_1.apply(&SelectedSelectOptions {
|
||||||
|
options: vec![option_1.clone(), option_2.clone()],
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
filter_1.apply(&SelectedSelectOptions {
|
||||||
|
options: vec![option_1.clone()],
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ use crate::services::field::select_option::*;
|
|||||||
use crate::services::field::{
|
use crate::services::field::{
|
||||||
default_type_option_builder_from_type, type_option_builder_from_json_str, DateChangesetParams, DateChangesetPayload,
|
default_type_option_builder_from_type, type_option_builder_from_json_str, DateChangesetParams, DateChangesetPayload,
|
||||||
};
|
};
|
||||||
|
use crate::services::row::AnyCellData;
|
||||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||||
use flowy_grid_data_model::revision::FieldRevision;
|
use flowy_grid_data_model::revision::FieldRevision;
|
||||||
use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
|
use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
|
||||||
@ -364,7 +365,8 @@ pub(crate) async fn get_select_option_handler(
|
|||||||
Some(field_rev) => {
|
Some(field_rev) => {
|
||||||
let cell_rev = editor.get_cell_rev(¶ms.row_id, ¶ms.field_id).await?;
|
let cell_rev = editor.get_cell_rev(¶ms.row_id, ¶ms.field_id).await?;
|
||||||
let type_option = select_option_operation(&field_rev)?;
|
let type_option = select_option_operation(&field_rev)?;
|
||||||
let option_context = type_option.select_option_cell_data(&cell_rev);
|
let any_cell_data: AnyCellData = cell_rev.try_into()?;
|
||||||
|
let option_context = type_option.selected_select_option(any_cell_data);
|
||||||
data_result(option_context)
|
data_result(option_context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,9 @@ use crate::services::row::AnyCellData;
|
|||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||||
use flowy_grid_data_model::parser::NotEmptyStr;
|
use flowy_grid_data_model::parser::NotEmptyStr;
|
||||||
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataEntry};
|
use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry};
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
pub const SELECTION_IDS_SEPARATOR: &str = ",";
|
pub const SELECTION_IDS_SEPARATOR: &str = ",";
|
||||||
|
|
||||||
@ -61,20 +60,18 @@ impl std::default::Default for SelectOptionColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_select_context_from(cell_rev: &Option<CellRevision>, options: &[SelectOption]) -> Vec<SelectOption> {
|
pub fn make_selected_select_options<T: TryInto<AnyCellData>>(
|
||||||
match cell_rev {
|
any_cell_data: T,
|
||||||
None => vec![],
|
options: &[SelectOption],
|
||||||
Some(cell_rev) => {
|
) -> Vec<SelectOption> {
|
||||||
if let Ok(type_option_cell_data) = AnyCellData::from_str(&cell_rev.data) {
|
if let Ok(type_option_cell_data) = any_cell_data.try_into() {
|
||||||
select_option_ids(type_option_cell_data.cell_data)
|
let ids = SelectOptionIds::from(type_option_cell_data.cell_data);
|
||||||
.into_iter()
|
ids.iter()
|
||||||
.flat_map(|option_id| options.iter().find(|option| option.id == option_id).cloned())
|
.flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned())
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
|
pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
|
||||||
@ -103,7 +100,7 @@ pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
|
|||||||
SelectOption::with_color(name, color)
|
SelectOption::with_color(name, color)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData;
|
fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData;
|
||||||
|
|
||||||
fn options(&self) -> &Vec<SelectOption>;
|
fn options(&self) -> &Vec<SelectOption>;
|
||||||
|
|
||||||
@ -143,22 +140,40 @@ pub fn select_option_color_from_index(index: usize) -> SelectOptionColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct SelectOptionIds(Vec<String>);
|
pub struct SelectOptionIds(Vec<String>);
|
||||||
|
|
||||||
|
impl SelectOptionIds {
|
||||||
|
pub fn into_inner(self) -> Vec<String> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
|
impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
|
||||||
type Error = FlowyError;
|
type Error = FlowyError;
|
||||||
|
|
||||||
fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
|
fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
|
||||||
let ids = select_option_ids(value.cell_data);
|
Ok(Self::from(value.cell_data))
|
||||||
Ok(Self(ids))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<String> for SelectOptionIds {
|
impl std::convert::From<String> for SelectOptionIds {
|
||||||
fn from(s: String) -> Self {
|
fn from(s: String) -> Self {
|
||||||
let ids = select_option_ids(s);
|
let ids = s
|
||||||
|
.split(SELECTION_IDS_SEPARATOR)
|
||||||
|
.map(|id| id.to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
Self(ids)
|
Self(ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<Option<String>> for SelectOptionIds {
|
||||||
|
fn from(s: Option<String>) -> Self {
|
||||||
|
match s {
|
||||||
|
None => Self { 0: vec![] },
|
||||||
|
Some(s) => Self::from(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for SelectOptionIds {
|
impl std::ops::Deref for SelectOptionIds {
|
||||||
type Target = Vec<String>;
|
type Target = Vec<String>;
|
||||||
|
|
||||||
@ -173,12 +188,6 @@ impl std::ops::DerefMut for SelectOptionIds {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_option_ids(data: String) -> Vec<String> {
|
|
||||||
data.split(SELECTION_IDS_SEPARATOR)
|
|
||||||
.map(|id| id.to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, ProtoBuf)]
|
#[derive(Clone, Debug, Default, ProtoBuf)]
|
||||||
pub struct SelectOptionCellChangesetPayload {
|
pub struct SelectOptionCellChangesetPayload {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
@ -314,3 +323,15 @@ impl TryInto<SelectOptionChangeset> for SelectOptionChangesetPayload {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SelectedSelectOptions {
|
||||||
|
pub(crate) options: Vec<SelectOption>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<SelectOptionCellData> for SelectedSelectOptions {
|
||||||
|
fn from(data: SelectOptionCellData) -> Self {
|
||||||
|
Self {
|
||||||
|
options: data.select_options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,8 +2,8 @@ use crate::entities::{FieldType, GridSelectOptionFilter};
|
|||||||
|
|
||||||
use crate::impl_type_option;
|
use crate::impl_type_option;
|
||||||
use crate::services::field::select_option::{
|
use crate::services::field::select_option::{
|
||||||
make_select_context_from, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData, SelectOptionIds,
|
make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData,
|
||||||
SelectOptionOperation, SELECTION_IDS_SEPARATOR,
|
SelectOptionIds, SelectOptionOperation, SelectedSelectOptions, SELECTION_IDS_SEPARATOR,
|
||||||
};
|
};
|
||||||
use crate::services::field::type_options::util::get_cell_data;
|
use crate::services::field::type_options::util::get_cell_data;
|
||||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||||
@ -30,8 +30,8 @@ pub struct MultiSelectTypeOption {
|
|||||||
impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
|
impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
|
||||||
|
|
||||||
impl SelectOptionOperation for MultiSelectTypeOption {
|
impl SelectOptionOperation for MultiSelectTypeOption {
|
||||||
fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
|
fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
|
||||||
let select_options = make_select_context_from(cell_rev, &self.options);
|
let select_options = make_selected_select_options(any_cell_data, &self.options);
|
||||||
SelectOptionCellData {
|
SelectOptionCellData {
|
||||||
options: self.options.clone(),
|
options: self.options.clone(),
|
||||||
select_options,
|
select_options,
|
||||||
@ -47,12 +47,13 @@ impl SelectOptionOperation for MultiSelectTypeOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
|
impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
|
||||||
fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
|
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
|
||||||
if !any_cell_data.is_multi_select() {
|
if !any_cell_data.is_multi_select() {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
let _ids: SelectOptionIds = any_cell_data.try_into()?;
|
|
||||||
Ok(false)
|
let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data));
|
||||||
|
Ok(filter.apply(&selected_options))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CellDataOperation<String> for MultiSelectTypeOption {
|
impl CellDataOperation<String> for MultiSelectTypeOption {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
use crate::entities::{FieldType, GridSelectOptionFilter};
|
use crate::entities::{FieldType, GridSelectOptionFilter};
|
||||||
|
|
||||||
use crate::impl_type_option;
|
use crate::impl_type_option;
|
||||||
use crate::services::field::select_option::{
|
use crate::services::field::select_option::{
|
||||||
make_select_context_from, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData, SelectOptionIds,
|
make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData,
|
||||||
SelectOptionOperation,
|
SelectOptionIds, SelectOptionOperation,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||||
use crate::services::row::{
|
use crate::services::row::{
|
||||||
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||||
@ -13,9 +11,7 @@ use crate::services::row::{
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flowy_derive::ProtoBuf;
|
use flowy_derive::ProtoBuf;
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
|
|
||||||
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
|
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// Single select
|
// Single select
|
||||||
@ -30,8 +26,8 @@ pub struct SingleSelectTypeOption {
|
|||||||
impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
|
impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
|
||||||
|
|
||||||
impl SelectOptionOperation for SingleSelectTypeOption {
|
impl SelectOptionOperation for SingleSelectTypeOption {
|
||||||
fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
|
fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
|
||||||
let select_options = make_select_context_from(cell_rev, &self.options);
|
let select_options = make_selected_select_options(any_cell_data, &self.options);
|
||||||
SelectOptionCellData {
|
SelectOptionCellData {
|
||||||
options: self.options.clone(),
|
options: self.options.clone(),
|
||||||
select_options,
|
select_options,
|
||||||
|
@ -83,6 +83,25 @@ impl std::convert::TryFrom<&CellRevision> for AnyCellData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::convert::TryFrom<&Option<CellRevision>> for AnyCellData {
|
||||||
|
type Error = FlowyError;
|
||||||
|
|
||||||
|
fn try_from(value: &Option<CellRevision>) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
None => Err(FlowyError::invalid_data().context("Expected CellRevision, but receive None")),
|
||||||
|
Some(cell_rev) => AnyCellData::try_from(cell_rev),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::TryFrom<Option<CellRevision>> for AnyCellData {
|
||||||
|
type Error = FlowyError;
|
||||||
|
|
||||||
|
fn try_from(value: Option<CellRevision>) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_from(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AnyCellData {
|
impl AnyCellData {
|
||||||
pub fn new(content: String, field_type: FieldType) -> Self {
|
pub fn new(content: String, field_type: FieldType) -> Self {
|
||||||
AnyCellData {
|
AnyCellData {
|
||||||
|
Reference in New Issue
Block a user