mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
test: add databaase event test (#2728)
* test: add tests and fix modify primary field bug * test: add more test * fix: tauri buiuld * chore: disable share link button
This commit is contained in:
parent
1b56538a2f
commit
ce8cee5637
@ -16,8 +16,10 @@ class TextCellDataPersistence implements CellDataPersistence<String> {
|
||||
|
||||
@override
|
||||
Future<Option<FlowyError>> save(String data) async {
|
||||
final fut =
|
||||
_cellBackendSvc.updateCell(cellContext: cellContext, data: data);
|
||||
final fut = _cellBackendSvc.updateCell(
|
||||
cellContext: cellContext,
|
||||
data: data,
|
||||
);
|
||||
return fut.then((result) {
|
||||
return result.fold(
|
||||
(l) => none(),
|
||||
|
@ -29,7 +29,6 @@ class FieldBackendService {
|
||||
|
||||
Future<Either<Unit, FlowyError>> updateField({
|
||||
String? name,
|
||||
FieldType? fieldType,
|
||||
bool? frozen,
|
||||
bool? visibility,
|
||||
double? width,
|
||||
@ -42,10 +41,6 @@ class FieldBackendService {
|
||||
payload.name = name;
|
||||
}
|
||||
|
||||
if (fieldType != null) {
|
||||
payload.fieldType = fieldType;
|
||||
}
|
||||
|
||||
if (frozen != null) {
|
||||
payload.frozen = frozen;
|
||||
}
|
||||
|
@ -124,13 +124,29 @@ class _FieldOperationList extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _actionCell(FieldAction action) {
|
||||
bool enable = true;
|
||||
|
||||
// If the field is primary, delete and duplicate are disabled.
|
||||
if (fieldInfo.field.isPrimary) {
|
||||
switch (action) {
|
||||
case FieldAction.hide:
|
||||
break;
|
||||
case FieldAction.duplicate:
|
||||
enable = false;
|
||||
break;
|
||||
case FieldAction.delete:
|
||||
enable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Flexible(
|
||||
child: SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FieldActionCell(
|
||||
fieldInfo: fieldInfo,
|
||||
action: action,
|
||||
enable: action != FieldAction.delete || !fieldInfo.field.isPrimary,
|
||||
enable: enable,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -72,5 +72,6 @@ class CheckboxCardCellState with _$CheckboxCardCellState {
|
||||
}
|
||||
|
||||
bool _isSelected(String? cellData) {
|
||||
// The backend use "Yes" and "No" to represent the checkbox cell data.
|
||||
return cellData == "Yes";
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/plugins/document/application/share_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
@ -104,11 +103,11 @@ class ShareActionList extends StatelessWidget {
|
||||
showMessageToast('Exported to: $exportPath');
|
||||
}
|
||||
break;
|
||||
case ShareAction.copyLink:
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.shareAction_workInProgress.tr(),
|
||||
).show(context);
|
||||
break;
|
||||
// case ShareAction.copyLink:
|
||||
// NavigatorAlertDialog(
|
||||
// title: LocaleKeys.shareAction_workInProgress.tr(),
|
||||
// ).show(context);
|
||||
// break;
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
@ -118,7 +117,7 @@ class ShareActionList extends StatelessWidget {
|
||||
|
||||
enum ShareAction {
|
||||
markdown,
|
||||
copyLink,
|
||||
// copyLink,
|
||||
}
|
||||
|
||||
class ShareActionWrapper extends ActionCell {
|
||||
@ -133,8 +132,8 @@ class ShareActionWrapper extends ActionCell {
|
||||
switch (inner) {
|
||||
case ShareAction.markdown:
|
||||
return LocaleKeys.shareAction_markdown.tr();
|
||||
case ShareAction.copyLink:
|
||||
return LocaleKeys.shareAction_copyLink.tr();
|
||||
// case ShareAction.copyLink:
|
||||
// return LocaleKeys.shareAction_copyLink.tr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,57 +0,0 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/filter/filter_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/text_filter.pb.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../util.dart';
|
||||
|
||||
void main() {
|
||||
late AppFlowyGridTest gridTest;
|
||||
setUpAll(() async {
|
||||
gridTest = await AppFlowyGridTest.ensureInitialized();
|
||||
});
|
||||
|
||||
test("create a text filter and then alter the filter's field)", () async {
|
||||
final context = await gridTest.createTestGrid();
|
||||
final service = FilterBackendService(viewId: context.gridView.id);
|
||||
final textField = context.textFieldContext();
|
||||
|
||||
// Create the filter menu bloc
|
||||
final menuBloc = GridFilterMenuBloc(
|
||||
fieldController: context.fieldController,
|
||||
viewId: context.gridView.id,
|
||||
)..add(const GridFilterMenuEvent.initial());
|
||||
|
||||
// Insert filter for the text field
|
||||
await service.insertTextFilter(
|
||||
fieldId: textField.id,
|
||||
condition: TextFilterConditionPB.TextIsEmpty,
|
||||
content: "",
|
||||
);
|
||||
await gridResponseFuture();
|
||||
assert(menuBloc.state.filters.length == 1);
|
||||
|
||||
// Edit the text field
|
||||
final loader = FieldTypeOptionLoader(
|
||||
viewId: context.gridView.id,
|
||||
field: textField.field,
|
||||
);
|
||||
|
||||
final editorBloc = FieldEditorBloc(
|
||||
isGroupField: false,
|
||||
loader: loader,
|
||||
field: textField.field,
|
||||
)..add(const FieldEditorEvent.initial());
|
||||
await gridResponseFuture();
|
||||
|
||||
// Alter the field type to Number
|
||||
editorBloc.add(const FieldEditorEvent.switchToField(FieldType.Number));
|
||||
await gridResponseFuture();
|
||||
|
||||
// Check the number of filters
|
||||
assert(menuBloc.state.filters.isEmpty);
|
||||
});
|
||||
}
|
31
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
31
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -99,7 +99,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
[[package]]
|
||||
name = "appflowy-integrate"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1024,7 +1024,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -1042,7 +1042,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-client-ws"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"collab-sync",
|
||||
@ -1060,7 +1060,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1086,7 +1086,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1098,7 +1098,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1115,7 +1115,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1134,7 +1134,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-persistence"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
@ -1154,7 +1154,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1173,6 +1173,7 @@ dependencies = [
|
||||
"rusoto_credential",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar 2.2.1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-retry",
|
||||
@ -1184,7 +1185,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-sync"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cbc2e0#cbc2e0acb8420dc997921bb3f56b99f9975c2aab"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"collab",
|
||||
@ -1789,7 +1790,7 @@ dependencies = [
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
"similar 1.3.0",
|
||||
"syn 1.0.109",
|
||||
"tera",
|
||||
"toml 0.5.11",
|
||||
@ -1818,6 +1819,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"appflowy-integrate",
|
||||
"bytes",
|
||||
"diesel",
|
||||
"flowy-config",
|
||||
"flowy-database2",
|
||||
"flowy-document2",
|
||||
@ -1839,6 +1841,7 @@ dependencies = [
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5055,6 +5058,12 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
|
@ -21,23 +21,13 @@ export abstract class TypeOptionParser<T> {
|
||||
export class FieldBackendService {
|
||||
constructor(public readonly viewId: string, public readonly fieldId: string) {}
|
||||
|
||||
updateField = (data: {
|
||||
name?: string;
|
||||
fieldType?: FieldType;
|
||||
frozen?: boolean;
|
||||
visibility?: boolean;
|
||||
width?: number;
|
||||
}) => {
|
||||
updateField = (data: { name?: string; frozen?: boolean; visibility?: boolean; width?: number }) => {
|
||||
const payload = FieldChangesetPB.fromObject({ view_id: this.viewId, field_id: this.fieldId });
|
||||
|
||||
if (data.name !== undefined) {
|
||||
payload.name = data.name;
|
||||
}
|
||||
|
||||
if (data.fieldType !== undefined) {
|
||||
payload.field_type = data.fieldType;
|
||||
}
|
||||
|
||||
if (data.frozen !== undefined) {
|
||||
payload.frozen = data.frozen;
|
||||
}
|
||||
|
1
frontend/rust-lib/Cargo.lock
generated
1
frontend/rust-lib/Cargo.lock
generated
@ -1864,6 +1864,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"dotenv",
|
||||
"flowy-core",
|
||||
"flowy-database2",
|
||||
"flowy-folder2",
|
||||
"flowy-net",
|
||||
"flowy-notification",
|
||||
|
@ -1,182 +0,0 @@
|
||||
use crate::entities::FieldType;
|
||||
|
||||
use crate::services::cell::TypeCellData;
|
||||
use crate::services::database::DatabaseEditor;
|
||||
use crate::services::field::{
|
||||
CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateCellData, DateTypeOptionPB,
|
||||
MultiSelectTypeOptionPB, NumberTypeOptionPB, RichTextTypeOptionPB, SingleSelectTypeOptionPB,
|
||||
URLCellData,
|
||||
};
|
||||
use database_model::{FieldRevision, TypeOptionDataDeserializer};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use indexmap::IndexMap;
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Map, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ExportField {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub field_type: i64,
|
||||
pub visibility: bool,
|
||||
pub width: i64,
|
||||
pub type_options: HashMap<String, Value>,
|
||||
pub is_primary: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct ExportCell {
|
||||
data: String,
|
||||
field_type: FieldType,
|
||||
}
|
||||
|
||||
impl From<&Arc<FieldRevision>> for ExportField {
|
||||
fn from(field_rev: &Arc<FieldRevision>) -> Self {
|
||||
let field_type = FieldType::from(field_rev.ty);
|
||||
let mut type_options: HashMap<String, Value> = HashMap::new();
|
||||
|
||||
field_rev
|
||||
.type_options
|
||||
.iter()
|
||||
.filter(|(k, _)| k == &&field_rev.ty.to_string())
|
||||
.for_each(|(k, s)| {
|
||||
let value = match field_type {
|
||||
FieldType::RichText => {
|
||||
let pb = RichTextTypeOptionPB::from_json_str(s);
|
||||
serde_json::to_value(pb).unwrap()
|
||||
},
|
||||
FieldType::Number => {
|
||||
let pb = NumberTypeOptionPB::from_json_str(s);
|
||||
let mut map = Map::new();
|
||||
map.insert("format".to_string(), json!(pb.format as u8));
|
||||
map.insert("scale".to_string(), json!(pb.scale));
|
||||
map.insert("symbol".to_string(), json!(pb.symbol));
|
||||
map.insert("name".to_string(), json!(pb.name));
|
||||
Value::Object(map)
|
||||
},
|
||||
FieldType::DateTime => {
|
||||
let pb = DateTypeOptionPB::from_json_str(s);
|
||||
let mut map = Map::new();
|
||||
map.insert("date_format".to_string(), json!(pb.date_format as u8));
|
||||
map.insert("time_format".to_string(), json!(pb.time_format as u8));
|
||||
map.insert("field_type".to_string(), json!(FieldType::DateTime as u8));
|
||||
Value::Object(map)
|
||||
},
|
||||
FieldType::SingleSelect => {
|
||||
let pb = SingleSelectTypeOptionPB::from_json_str(s);
|
||||
let value = serde_json::to_string(&pb).unwrap();
|
||||
let mut map = Map::new();
|
||||
map.insert("content".to_string(), Value::String(value));
|
||||
Value::Object(map)
|
||||
},
|
||||
FieldType::MultiSelect => {
|
||||
let pb = MultiSelectTypeOptionPB::from_json_str(s);
|
||||
let value = serde_json::to_string(&pb).unwrap();
|
||||
let mut map = Map::new();
|
||||
map.insert("content".to_string(), Value::String(value));
|
||||
Value::Object(map)
|
||||
},
|
||||
FieldType::Checkbox => {
|
||||
let pb = CheckboxTypeOptionPB::from_json_str(s);
|
||||
serde_json::to_value(pb).unwrap()
|
||||
},
|
||||
FieldType::URL => {
|
||||
let pb = RichTextTypeOptionPB::from_json_str(s);
|
||||
serde_json::to_value(pb).unwrap()
|
||||
},
|
||||
FieldType::Checklist => {
|
||||
let pb = ChecklistTypeOptionPB::from_json_str(s);
|
||||
let value = serde_json::to_string(&pb).unwrap();
|
||||
let mut map = Map::new();
|
||||
map.insert("content".to_string(), Value::String(value));
|
||||
Value::Object(map)
|
||||
},
|
||||
};
|
||||
type_options.insert(k.clone(), value);
|
||||
});
|
||||
Self {
|
||||
id: field_rev.id.clone(),
|
||||
name: field_rev.name.clone(),
|
||||
field_type: field_rev.ty as i64,
|
||||
visibility: true,
|
||||
width: 100,
|
||||
type_options,
|
||||
is_primary: field_rev.is_primary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CSVExport;
|
||||
impl CSVExport {
|
||||
pub async fn export_database(
|
||||
&self,
|
||||
view_id: &str,
|
||||
database_editor: &Arc<DatabaseEditor>,
|
||||
) -> FlowyResult<String> {
|
||||
let mut wtr = csv::Writer::from_writer(vec![]);
|
||||
let row_revs = database_editor.get_all_row_revs(view_id).await?;
|
||||
let field_revs = database_editor.get_field_revs(None).await?;
|
||||
|
||||
// Write fields
|
||||
let field_records = field_revs
|
||||
.iter()
|
||||
.map(|field| ExportField::from(field))
|
||||
.map(|field| serde_json::to_string(&field).unwrap())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
wtr
|
||||
.write_record(&field_records)
|
||||
.map_err(|e| FlowyError::internal().context(e))?;
|
||||
|
||||
// Write rows
|
||||
let mut field_by_field_id = IndexMap::new();
|
||||
field_revs.into_iter().for_each(|field| {
|
||||
field_by_field_id.insert(field.id.clone(), field);
|
||||
});
|
||||
for row_rev in row_revs {
|
||||
let cells = field_by_field_id
|
||||
.iter()
|
||||
.map(|(field_id, field)| {
|
||||
let field_type = FieldType::from(field.ty);
|
||||
let data = row_rev
|
||||
.cells
|
||||
.get(field_id)
|
||||
.map(|cell| TypeCellData::try_from(cell))
|
||||
.map(|data| {
|
||||
data
|
||||
.map(|data| match field_type {
|
||||
FieldType::DateTime => {
|
||||
match serde_json::from_str::<DateCellData>(&data.cell_str) {
|
||||
Ok(cell_data) => cell_data.timestamp.unwrap_or_default().to_string(),
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
},
|
||||
FieldType::URL => match serde_json::from_str::<URLCellData>(&data.cell_str) {
|
||||
Ok(cell_data) => cell_data.content,
|
||||
Err(_) => "".to_string(),
|
||||
},
|
||||
_ => data.cell_str,
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
let cell = ExportCell { data, field_type };
|
||||
serde_json::to_string(&cell).unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Err(e) = wtr.write_record(&cells) {
|
||||
tracing::warn!("CSV failed to write record: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let data = wtr
|
||||
.into_inner()
|
||||
.map_err(|e| FlowyError::internal().context(e))?;
|
||||
let csv = String::from_utf8(data).map_err(|e| FlowyError::internal().context(e))?;
|
||||
Ok(csv)
|
||||
}
|
||||
}
|
@ -155,6 +155,8 @@ pub struct CreateFieldPayloadPB {
|
||||
#[pb(index = 2)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
/// If the type_option_data is not empty, it will be used to create the field.
|
||||
/// Otherwise, the default value will be used.
|
||||
#[pb(index = 3, one_of)]
|
||||
pub type_option_data: Option<Vec<u8>>,
|
||||
}
|
||||
@ -163,6 +165,8 @@ pub struct CreateFieldPayloadPB {
|
||||
pub struct CreateFieldParams {
|
||||
pub view_id: String,
|
||||
pub field_type: FieldType,
|
||||
/// If the type_option_data is not empty, it will be used to create the field.
|
||||
/// Otherwise, the default value will be used.
|
||||
pub type_option_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
@ -189,9 +193,6 @@ pub struct UpdateFieldTypePayloadPB {
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub create_if_not_exist: bool,
|
||||
}
|
||||
|
||||
pub struct EditFieldParams {
|
||||
@ -401,18 +402,13 @@ pub struct FieldChangesetPB {
|
||||
pub desc: Option<String>,
|
||||
|
||||
#[pb(index = 5, one_of)]
|
||||
pub field_type: Option<FieldType>,
|
||||
|
||||
#[pb(index = 6, one_of)]
|
||||
pub frozen: Option<bool>,
|
||||
|
||||
#[pb(index = 7, one_of)]
|
||||
#[pb(index = 6, one_of)]
|
||||
pub visibility: Option<bool>,
|
||||
|
||||
#[pb(index = 8, one_of)]
|
||||
#[pb(index = 7, one_of)]
|
||||
pub width: Option<i32>,
|
||||
// #[pb(index = 9, one_of)]
|
||||
// pub type_option_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl TryInto<FieldChangesetParams> for FieldChangesetPB {
|
||||
@ -421,7 +417,6 @@ impl TryInto<FieldChangesetParams> for FieldChangesetPB {
|
||||
fn try_into(self) -> Result<FieldChangesetParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
let field_type = self.field_type.map(FieldType::from);
|
||||
// if let Some(type_option_data) = self.type_option_data.as_ref() {
|
||||
// if type_option_data.is_empty() {
|
||||
// return Err(ErrorCode::TypeOptionDataIsEmpty);
|
||||
@ -433,7 +428,6 @@ impl TryInto<FieldChangesetParams> for FieldChangesetPB {
|
||||
view_id: view_id.0,
|
||||
name: self.name,
|
||||
desc: self.desc,
|
||||
field_type,
|
||||
frozen: self.frozen,
|
||||
visibility: self.visibility,
|
||||
width: self.width,
|
||||
@ -452,8 +446,6 @@ pub struct FieldChangesetParams {
|
||||
|
||||
pub desc: Option<String>,
|
||||
|
||||
pub field_type: Option<FieldType>,
|
||||
|
||||
pub frozen: Option<bool>,
|
||||
|
||||
pub visibility: Option<bool>,
|
||||
|
@ -249,7 +249,7 @@ pub(crate) async fn get_field_type_option_data_handler(
|
||||
}
|
||||
}
|
||||
|
||||
/// Create FieldMeta and save it. Return the FieldTypeOptionData.
|
||||
/// Create TypeOptionPB and save it. Return the FieldTypeOptionData.
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn create_field_type_option_data_handler(
|
||||
data: AFPluginData<CreateFieldPayloadPB>,
|
||||
|
@ -10,7 +10,7 @@ use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
||||
use parking_lot::Mutex;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
|
||||
use flowy_error::{internal_error, FlowyError, FlowyResult};
|
||||
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_task::TaskDispatcher;
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
|
||||
@ -199,32 +199,32 @@ impl DatabaseEditor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of fields of the view.
|
||||
/// If `field_ids` is not provided, all the fields will be returned in the order of the field that
|
||||
/// defined in the view. Otherwise, the fields will be returned in the order of the `field_ids`.
|
||||
pub fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Vec<Field> {
|
||||
self.database.lock().get_fields(view_id, field_ids)
|
||||
let database = self.database.lock();
|
||||
let field_ids = field_ids.unwrap_or_else(|| {
|
||||
database
|
||||
.fields
|
||||
.get_all_field_orders()
|
||||
.into_iter()
|
||||
.map(|field| field.id)
|
||||
.collect()
|
||||
});
|
||||
database.get_fields(view_id, Some(field_ids))
|
||||
}
|
||||
|
||||
pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
|
||||
let is_primary = self
|
||||
.database
|
||||
.lock()
|
||||
.fields
|
||||
.get_field(¶ms.field_id)
|
||||
.map(|field| field.is_primary)
|
||||
.unwrap_or(false);
|
||||
self
|
||||
.database
|
||||
.lock()
|
||||
.fields
|
||||
.update_field(¶ms.field_id, |mut update| {
|
||||
update = update
|
||||
.update_field(¶ms.field_id, |update| {
|
||||
update
|
||||
.set_name_if_not_none(params.name)
|
||||
.set_width_at_if_not_none(params.width.map(|value| value as i64))
|
||||
.set_visibility_if_not_none(params.visibility);
|
||||
if is_primary {
|
||||
tracing::warn!("Cannot update primary field type");
|
||||
} else {
|
||||
update.set_field_type_if_not_none(params.field_type.map(|field_type| field_type.into()));
|
||||
}
|
||||
});
|
||||
self
|
||||
.notify_did_update_database_field(¶ms.field_id)
|
||||
@ -233,6 +233,21 @@ impl DatabaseEditor {
|
||||
}
|
||||
|
||||
pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||
let is_primary = self
|
||||
.database
|
||||
.lock()
|
||||
.fields
|
||||
.get_field(field_id)
|
||||
.map(|field| field.is_primary)
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_primary {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"Can not delete primary field",
|
||||
));
|
||||
}
|
||||
|
||||
let database_id = {
|
||||
let database = self.database.lock();
|
||||
database.delete_field(field_id);
|
||||
@ -283,6 +298,13 @@ impl DatabaseEditor {
|
||||
match field {
|
||||
None => {},
|
||||
Some(field) => {
|
||||
if field.is_primary {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"Can not update primary field's field type",
|
||||
));
|
||||
}
|
||||
|
||||
let old_field_type = FieldType::from(field.field_type);
|
||||
let old_type_option = field.get_any_type_option(old_field_type.clone());
|
||||
let new_type_option = field
|
||||
@ -312,6 +334,21 @@ impl DatabaseEditor {
|
||||
}
|
||||
|
||||
pub async fn duplicate_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> {
|
||||
let is_primary = self
|
||||
.database
|
||||
.lock()
|
||||
.fields
|
||||
.get_field(field_id)
|
||||
.map(|field| field.is_primary)
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_primary {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::Internal,
|
||||
"Can not duplicate primary field",
|
||||
));
|
||||
}
|
||||
|
||||
let value = self
|
||||
.database
|
||||
.lock()
|
||||
|
@ -90,7 +90,9 @@ impl CellDataDecoder for CheckboxTypeOption {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
self.parse_cell(cell)
|
||||
let cell = self.parse_cell(cell);
|
||||
println!("cell: {:?}", cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
|
||||
|
@ -233,30 +233,6 @@ async fn grid_switch_from_checkbox_to_text_test() {
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
// Test when switching the current field from Checkbox to Text test
|
||||
// input:
|
||||
// "Yes" -> check
|
||||
// "" -> unchecked
|
||||
#[tokio::test]
|
||||
async fn grid_switch_from_text_to_checkbox_test() {
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let field = test.get_first_field(FieldType::RichText).clone();
|
||||
|
||||
let scripts = vec![
|
||||
SwitchToField {
|
||||
field_id: field.id.clone(),
|
||||
new_field_type: FieldType::Checkbox,
|
||||
},
|
||||
AssertCellContent {
|
||||
field_id: field.id.clone(),
|
||||
row_index: 0,
|
||||
from_field_type: FieldType::RichText,
|
||||
expected_content: "".to_string(),
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
// Test when switching the current field from Date to Text test
|
||||
// input:
|
||||
// 1647251762 -> Mar 14,2022 (This string will be different base on current data setting)
|
||||
|
@ -41,10 +41,14 @@ pub(crate) async fn open_workspace_handler(
|
||||
match params.value {
|
||||
None => Err(FlowyError::workspace_id().context("workspace id should not be empty")),
|
||||
Some(workspace_id) => {
|
||||
let workspace = folder.open_workspace(&workspace_id).await?;
|
||||
let views = folder.get_workspace_views(&workspace_id).await?;
|
||||
let workspace_pb: WorkspacePB = (workspace, views).into();
|
||||
data_result_ok(workspace_pb)
|
||||
if workspace_id.is_empty() {
|
||||
return Err(FlowyError::workspace_id().context("workspace id should not be empty"));
|
||||
} else {
|
||||
let workspace = folder.open_workspace(&workspace_id).await?;
|
||||
let views = folder.get_workspace_views(&workspace_id).await?;
|
||||
let workspace_pb: WorkspacePB = (workspace, views).into();
|
||||
data_result_ok(workspace_pb)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -68,7 +72,7 @@ pub(crate) async fn read_workspaces_handler(
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(folder), err)]
|
||||
pub async fn read_cur_workspace_setting_handler(
|
||||
pub async fn read_current_workspace_setting_handler(
|
||||
folder: AFPluginState<Arc<Folder2Manager>>,
|
||||
) -> DataResult<WorkspaceSettingPB, FlowyError> {
|
||||
let workspace = folder.get_current_workspace().await?;
|
||||
|
@ -13,7 +13,7 @@ pub fn init(folder: Arc<Folder2Manager>) -> AFPlugin {
|
||||
.event(FolderEvent::CreateWorkspace, create_workspace_handler)
|
||||
.event(
|
||||
FolderEvent::GetCurrentWorkspace,
|
||||
read_cur_workspace_setting_handler,
|
||||
read_current_workspace_setting_handler,
|
||||
)
|
||||
.event(FolderEvent::ReadAllWorkspaces, read_workspaces_handler)
|
||||
.event(FolderEvent::OpenWorkspace, open_workspace_handler)
|
||||
|
@ -10,6 +10,7 @@ flowy-core = { path = "../flowy-core" }
|
||||
flowy-user = { path = "../flowy-user"}
|
||||
flowy-net = { path = "../flowy-net"}
|
||||
flowy-folder2 = { path = "../flowy-folder2", features = ["test_helper"] }
|
||||
flowy-database2 = { path = "../flowy-database2" }
|
||||
lib-dispatch = { path = "../lib-dispatch" }
|
||||
lib-ot = { path = "../../../shared-lib/lib-ot" }
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
|
@ -5,10 +5,10 @@ use nanoid::nanoid;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use flowy_core::{AppFlowyCore, AppFlowyCoreConfig};
|
||||
use flowy_folder2::entities::{
|
||||
CreateViewPayloadPB, RepeatedViewIdPB, ViewIdPB, ViewPB, WorkspaceSettingPB,
|
||||
};
|
||||
use flowy_database2::entities::*;
|
||||
use flowy_folder2::entities::*;
|
||||
use flowy_user::entities::{AuthTypePB, UserProfilePB};
|
||||
use flowy_user::errors::FlowyError;
|
||||
|
||||
use crate::event_builder::EventBuilder;
|
||||
use crate::user_event::{async_sign_up, init_user_setting, SignUpContext};
|
||||
@ -113,6 +113,210 @@ impl FlowyCoreTest {
|
||||
.parse::<flowy_folder2::entities::ViewPB>()
|
||||
}
|
||||
|
||||
pub async fn create_grid(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
|
||||
let payload = CreateViewPayloadPB {
|
||||
parent_view_id: parent_id.to_string(),
|
||||
name,
|
||||
desc: "".to_string(),
|
||||
thumbnail: None,
|
||||
layout: ViewLayoutPB::Grid,
|
||||
initial_data,
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
};
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_folder2::event_map::FolderEvent::CreateView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<flowy_folder2::entities::ViewPB>()
|
||||
}
|
||||
|
||||
pub async fn get_database(&self, view_id: &str) -> DatabasePB {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::GetDatabase)
|
||||
.payload(DatabaseViewIdPB {
|
||||
value: view_id.to_string(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<flowy_database2::entities::DatabasePB>()
|
||||
}
|
||||
|
||||
pub async fn get_all_database_fields(&self, view_id: &str) -> RepeatedFieldPB {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::GetFields)
|
||||
.payload(GetFieldPayloadPB {
|
||||
view_id: view_id.to_string(),
|
||||
field_ids: None,
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<RepeatedFieldPB>()
|
||||
}
|
||||
|
||||
pub async fn create_field(&self, view_id: &str, field_type: FieldType) -> FieldPB {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::CreateTypeOption)
|
||||
.payload(CreateFieldPayloadPB {
|
||||
view_id: view_id.to_string(),
|
||||
field_type,
|
||||
type_option_data: None,
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<TypeOptionPB>()
|
||||
.field
|
||||
}
|
||||
|
||||
pub async fn update_field(&self, changeset: FieldChangesetPB) {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::UpdateField)
|
||||
.payload(changeset)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn delete_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::DeleteField)
|
||||
.payload(DeleteFieldPayloadPB {
|
||||
view_id: view_id.to_string(),
|
||||
field_id: field_id.to_string(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
}
|
||||
|
||||
pub async fn update_field_type(
|
||||
&self,
|
||||
view_id: &str,
|
||||
field_id: &str,
|
||||
field_type: FieldType,
|
||||
) -> Option<FlowyError> {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::UpdateFieldType)
|
||||
.payload(UpdateFieldTypePayloadPB {
|
||||
view_id: view_id.to_string(),
|
||||
field_id: field_id.to_string(),
|
||||
field_type,
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
}
|
||||
|
||||
pub async fn duplicate_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::DuplicateField)
|
||||
.payload(DuplicateFieldPayloadPB {
|
||||
view_id: view_id.to_string(),
|
||||
field_id: field_id.to_string(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
}
|
||||
|
||||
pub async fn create_row(
|
||||
&self,
|
||||
view_id: &str,
|
||||
start_row_id: Option<String>,
|
||||
data: Option<RowDataPB>,
|
||||
) -> RowPB {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::CreateRow)
|
||||
.payload(CreateRowPayloadPB {
|
||||
view_id: view_id.to_string(),
|
||||
start_row_id,
|
||||
group_id: None,
|
||||
data,
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<RowPB>()
|
||||
}
|
||||
|
||||
pub async fn get_row(&self, view_id: &str, row_id: &str) -> RowPB {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::GetRow)
|
||||
.payload(RowIdPB {
|
||||
view_id: view_id.to_string(),
|
||||
row_id: row_id.to_string(),
|
||||
group_id: None,
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<RowPB>()
|
||||
}
|
||||
|
||||
pub async fn duplicate_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::DuplicateRow)
|
||||
.payload(RowIdPB {
|
||||
view_id: view_id.to_string(),
|
||||
row_id: row_id.to_string(),
|
||||
group_id: None,
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
}
|
||||
|
||||
pub async fn update_cell(&self, changeset: CellChangesetPB) -> Option<FlowyError> {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::UpdateCell)
|
||||
.payload(changeset)
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
}
|
||||
|
||||
pub async fn get_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> CellPB {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::GetCell)
|
||||
.payload(CellIdPB {
|
||||
view_id: view_id.to_string(),
|
||||
row_id: row_id.to_string(),
|
||||
field_id: field_id.to_string(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<CellPB>()
|
||||
}
|
||||
|
||||
pub async fn insert_option(
|
||||
&self,
|
||||
view_id: &str,
|
||||
field_id: &str,
|
||||
row_id: &str,
|
||||
name: &str,
|
||||
) -> Option<FlowyError> {
|
||||
let option = EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::CreateSelectOption)
|
||||
.payload(CreateSelectOptionPayloadPB {
|
||||
field_id: field_id.to_string(),
|
||||
view_id: view_id.to_string(),
|
||||
option_name: name.to_string(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<SelectOptionPB>();
|
||||
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::InsertOrUpdateSelectOption)
|
||||
.payload(RepeatedSelectOptionPayload {
|
||||
view_id: view_id.to_string(),
|
||||
field_id: field_id.to_string(),
|
||||
row_id: row_id.to_string(),
|
||||
items: vec![option],
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
}
|
||||
|
||||
pub async fn get_view(&self, view_id: &str) -> ViewPB {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(flowy_folder2::event_map::FolderEvent::ReadView)
|
||||
|
@ -1 +1,291 @@
|
||||
use bytes::Bytes;
|
||||
use flowy_database2::entities::{
|
||||
CellChangesetPB, DatabaseLayoutPB, DatabaseViewIdPB, FieldType, SelectOptionCellDataPB,
|
||||
};
|
||||
use flowy_test::event_builder::EventBuilder;
|
||||
use flowy_test::FlowyCoreTest;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_database_id_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
// The view id can be used to get the database id.
|
||||
let database_id = EventBuilder::new(test.clone())
|
||||
.event(flowy_database2::event_map::DatabaseEvent::GetDatabaseId)
|
||||
.payload(DatabaseViewIdPB {
|
||||
value: grid_view.id.clone(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<flowy_database2::entities::DatabaseIdPB>()
|
||||
.value;
|
||||
|
||||
assert_ne!(database_id, grid_view.id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_database_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
let database = test.get_database(&grid_view.id).await;
|
||||
assert_eq!(database.fields.len(), 3);
|
||||
assert_eq!(database.rows.len(), 3);
|
||||
assert_eq!(database.layout_type, DatabaseLayoutPB::Grid);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_field_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
assert_eq!(fields[0].field_type, FieldType::RichText);
|
||||
assert_eq!(fields[1].field_type, FieldType::SingleSelect);
|
||||
assert_eq!(fields[2].field_type, FieldType::Checkbox);
|
||||
assert_eq!(fields.len(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_field_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
test.create_field(&grid_view.id, FieldType::Checkbox).await;
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
assert_eq!(fields.len(), 4);
|
||||
assert_eq!(fields[3].field_type, FieldType::Checkbox);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_field_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
assert_eq!(fields[0].field_type, FieldType::RichText);
|
||||
assert_eq!(fields[1].field_type, FieldType::SingleSelect);
|
||||
assert_eq!(fields[2].field_type, FieldType::Checkbox);
|
||||
|
||||
let error = test.delete_field(&grid_view.id, &fields[1].id).await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
assert_eq!(fields.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_primary_field_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
// the primary field is not allowed to be deleted.
|
||||
assert!(fields[0].is_primary);
|
||||
let error = test.delete_field(&grid_view.id, &fields[0].id).await;
|
||||
assert!(error.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_field_type_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
let error = test
|
||||
.update_field_type(&grid_view.id, &fields[1].id, FieldType::Checklist)
|
||||
.await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
assert_eq!(fields[1].field_type, FieldType::Checklist);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_primary_field_type_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
// the primary field is not allowed to be deleted.
|
||||
assert!(fields[0].is_primary);
|
||||
|
||||
// the primary field is not allowed to be updated.
|
||||
let error = test
|
||||
.update_field_type(&grid_view.id, &fields[0].id, FieldType::Checklist)
|
||||
.await;
|
||||
assert!(error.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn duplicate_field_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
// the primary field is not allowed to be updated.
|
||||
let error = test.duplicate_field(&grid_view.id, &fields[1].id).await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
assert_eq!(fields.len(), 4);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn duplicate_primary_field_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
// the primary field is not allowed to be duplicated.
|
||||
let error = test.duplicate_field(&grid_view.id, &fields[0].id).await;
|
||||
assert!(error.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_row_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
let _ = test.create_row(&grid_view.id, None, None).await;
|
||||
let database = test.get_database(&grid_view.id).await;
|
||||
assert_eq!(database.rows.len(), 4);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn duplicate_row_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
let database = test.get_database(&grid_view.id).await;
|
||||
let error = test
|
||||
.duplicate_row(&grid_view.id, &database.rows[0].id)
|
||||
.await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let database = test.get_database(&grid_view.id).await;
|
||||
assert_eq!(database.rows.len(), 4);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_text_cell_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
let database = test.get_database(&grid_view.id).await;
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
|
||||
let row_id = database.rows[0].id.clone();
|
||||
let field_id = fields[0].id.clone();
|
||||
assert_eq!(fields[0].field_type, FieldType::RichText);
|
||||
|
||||
// Update the first cell of the first row.
|
||||
let error = test
|
||||
.update_cell(CellChangesetPB {
|
||||
view_id: grid_view.id.clone(),
|
||||
row_id: row_id.clone(),
|
||||
field_id: field_id.clone(),
|
||||
cell_changeset: "hello world".to_string(),
|
||||
})
|
||||
.await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let cell = test.get_cell(&grid_view.id, &row_id, &field_id).await;
|
||||
let s = String::from_utf8(cell.data).unwrap();
|
||||
assert_eq!(s, "hello world");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_checkbox_cell_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
let database = test.get_database(&grid_view.id).await;
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
|
||||
let row_id = database.rows[0].id.clone();
|
||||
let field_id = fields[2].id.clone();
|
||||
assert_eq!(fields[2].field_type, FieldType::Checkbox);
|
||||
|
||||
for input in vec!["yes", "true", "1"] {
|
||||
let error = test
|
||||
.update_cell(CellChangesetPB {
|
||||
view_id: grid_view.id.clone(),
|
||||
row_id: row_id.clone(),
|
||||
field_id: field_id.clone(),
|
||||
cell_changeset: input.to_string(),
|
||||
})
|
||||
.await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let cell = test.get_cell(&grid_view.id, &row_id, &field_id).await;
|
||||
let output = String::from_utf8(cell.data).unwrap();
|
||||
assert_eq!(output, "Yes");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_single_select_cell_event_test() {
|
||||
let test = FlowyCoreTest::new_with_user().await;
|
||||
let current_workspace = test.get_current_workspace().await.workspace;
|
||||
let grid_view = test
|
||||
.create_grid(¤t_workspace.id, "my grid view".to_owned(), vec![])
|
||||
.await;
|
||||
let database = test.get_database(&grid_view.id).await;
|
||||
let fields = test.get_all_database_fields(&grid_view.id).await.items;
|
||||
let row_id = database.rows[0].id.clone();
|
||||
let field_id = fields[1].id.clone();
|
||||
assert_eq!(fields[1].field_type, FieldType::SingleSelect);
|
||||
|
||||
let error = test
|
||||
.insert_option(&grid_view.id, &field_id, &row_id, "task 1")
|
||||
.await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let cell = test.get_cell(&grid_view.id, &row_id, &field_id).await;
|
||||
let select_option_cell = SelectOptionCellDataPB::try_from(Bytes::from(cell.data)).unwrap();
|
||||
|
||||
assert_eq!(select_option_cell.options.len(), 1);
|
||||
assert_eq!(select_option_cell.select_options.len(), 1);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user