chore: add checkbox & select option filter tests

This commit is contained in:
appflowy 2022-07-07 22:33:34 +08:00
parent e8e719b73f
commit 0d5f0d29d9
6 changed files with 141 additions and 47 deletions

View File

@ -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_error::ErrorCode;
use flowy_grid_data_model::revision::GridFilterRevision;
@ -9,13 +10,34 @@ pub struct GridSelectOptionFilter {
#[pb(index = 1)]
pub condition: SelectOptionCondition,
#[pb(index = 2, one_of)]
pub content: Option<String>,
#[pb(index = 2)]
pub option_ids: Vec<String>,
}
impl GridSelectOptionFilter {
pub fn apply(&self, _ids: &SelectOptionIds) -> bool {
false
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
}
SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(),
SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(),
}
}
}
@ -56,9 +78,42 @@ impl std::convert::TryFrom<u8> for SelectOptionCondition {
impl std::convert::From<Arc<GridFilterRevision>> for GridSelectOptionFilter {
fn from(rev: Arc<GridFilterRevision>) -> Self {
let ids = SelectOptionIds::from(rev.content.clone());
GridSelectOptionFilter {
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,
);
}
}

View File

@ -4,6 +4,7 @@ use crate::services::field::select_option::*;
use crate::services::field::{
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_grid_data_model::revision::FieldRevision;
use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
@ -364,7 +365,8 @@ pub(crate) async fn get_select_option_handler(
Some(field_rev) => {
let cell_rev = editor.get_cell_rev(&params.row_id, &params.field_id).await?;
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)
}
}

View File

@ -4,10 +4,9 @@ use crate::services::row::AnyCellData;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
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 serde::{Deserialize, Serialize};
use std::str::FromStr;
pub const SELECTION_IDS_SEPARATOR: &str = ",";
@ -61,19 +60,17 @@ impl std::default::Default for SelectOptionColor {
}
}
pub fn make_select_context_from(cell_rev: &Option<CellRevision>, options: &[SelectOption]) -> Vec<SelectOption> {
match cell_rev {
None => vec![],
Some(cell_rev) => {
if let Ok(type_option_cell_data) = AnyCellData::from_str(&cell_rev.data) {
select_option_ids(type_option_cell_data.cell_data)
.into_iter()
.flat_map(|option_id| options.iter().find(|option| option.id == option_id).cloned())
.collect()
} else {
vec![]
}
}
pub fn make_selected_select_options<T: TryInto<AnyCellData>>(
any_cell_data: T,
options: &[SelectOption],
) -> Vec<SelectOption> {
if let Ok(type_option_cell_data) = any_cell_data.try_into() {
let ids = SelectOptionIds::from(type_option_cell_data.cell_data);
ids.iter()
.flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned())
.collect()
} else {
vec![]
}
}
@ -103,7 +100,7 @@ pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
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>;
@ -143,22 +140,40 @@ pub fn select_option_color_from_index(index: usize) -> SelectOptionColor {
}
}
pub struct SelectOptionIds(Vec<String>);
impl SelectOptionIds {
pub fn into_inner(self) -> Vec<String> {
self.0
}
}
impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
type Error = FlowyError;
fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
let ids = select_option_ids(value.cell_data);
Ok(Self(ids))
Ok(Self::from(value.cell_data))
}
}
impl std::convert::From<String> for SelectOptionIds {
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)
}
}
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 {
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)]
pub struct SelectOptionCellChangesetPayload {
#[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,
}
}
}

View File

@ -2,8 +2,8 @@ use crate::entities::{FieldType, GridSelectOptionFilter};
use crate::impl_type_option;
use crate::services::field::select_option::{
make_select_context_from, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData, SelectOptionIds,
SelectOptionOperation, SELECTION_IDS_SEPARATOR,
make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData,
SelectOptionIds, SelectOptionOperation, SelectedSelectOptions, SELECTION_IDS_SEPARATOR,
};
use crate::services::field::type_options::util::get_cell_data;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
@ -30,8 +30,8 @@ pub struct MultiSelectTypeOption {
impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
impl SelectOptionOperation for MultiSelectTypeOption {
fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
let select_options = make_select_context_from(cell_rev, &self.options);
fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
let select_options = make_selected_select_options(any_cell_data, &self.options);
SelectOptionCellData {
options: self.options.clone(),
select_options,
@ -47,12 +47,13 @@ impl SelectOptionOperation 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() {
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 {

View File

@ -1,11 +1,9 @@
use crate::entities::{FieldType, GridSelectOptionFilter};
use crate::impl_type_option;
use crate::services::field::select_option::{
make_select_context_from, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData, SelectOptionIds,
SelectOptionOperation,
make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData,
SelectOptionIds, SelectOptionOperation,
};
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use crate::services::row::{
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
@ -13,9 +11,7 @@ use crate::services::row::{
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
use serde::{Deserialize, Serialize};
// Single select
@ -30,8 +26,8 @@ pub struct SingleSelectTypeOption {
impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
impl SelectOptionOperation for SingleSelectTypeOption {
fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
let select_options = make_select_context_from(cell_rev, &self.options);
fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
let select_options = make_selected_select_options(any_cell_data, &self.options);
SelectOptionCellData {
options: self.options.clone(),
select_options,

View File

@ -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 {
pub fn new(content: String, field_type: FieldType) -> Self {
AnyCellData {