mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-09-11 11:15:53 +03:00
Fix/grid group (#1787)
* ci: config rust log * chore: rename flowy-sdk to appflowy-core * fix: create group after editing the url * fix: start listen on new group * chore: add tests * refactor: mock data * ci: update command
This commit is contained in:
parent
d09574951b
commit
069519589e
2
.github/workflows/rust_ci.yaml
vendored
2
.github/workflows/rust_ci.yaml
vendored
@ -59,7 +59,7 @@ jobs:
|
||||
- name: Build FlowySDK
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make --profile development-linux-x86_64 appflowy-sdk-dev
|
||||
cargo make --profile development-linux-x86_64 appflowy-core-dev
|
||||
|
||||
- name: rustfmt rust-lib
|
||||
run: cargo fmt --all -- --check
|
||||
|
2
frontend/.vscode/launch.json
vendored
2
frontend/.vscode/launch.json
vendored
@ -23,7 +23,7 @@
|
||||
"type": "dart",
|
||||
"preLaunchTask": "AF: build_flowy_sdk",
|
||||
"env": {
|
||||
"RUST_LOG": "info"
|
||||
"RUST_LOG": "debug"
|
||||
},
|
||||
"cwd": "${workspaceRoot}/app_flowy"
|
||||
},
|
||||
|
2
frontend/.vscode/tasks.json
vendored
2
frontend/.vscode/tasks.json
vendored
@ -48,7 +48,7 @@
|
||||
{
|
||||
"label": "AF: build_flowy_sdk_for_android",
|
||||
"type": "shell",
|
||||
"command": "cargo make --profile development-android appflowy-sdk-dev-android",
|
||||
"command": "cargo make --profile development-android appflowy-core-dev-android",
|
||||
"group": "build",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
|
@ -18,7 +18,7 @@ on_error_task = "catch"
|
||||
run_task = { name = ["restore-crate-type"] }
|
||||
|
||||
[env]
|
||||
RUST_LOG = "info"
|
||||
RUST_LOG = "debug"
|
||||
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
|
@ -186,43 +186,20 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void initializeGroups(List<GroupPB> groupsData) {
|
||||
void initializeGroups(List<GroupPB> groups) {
|
||||
for (var controller in groupControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
groupControllers.clear();
|
||||
boardController.clear();
|
||||
|
||||
//
|
||||
List<AppFlowyGroupData> groups = groupsData
|
||||
boardController.addGroups(groups
|
||||
.where((group) => fieldController.getField(group.fieldId) != null)
|
||||
.map((group) {
|
||||
return AppFlowyGroupData(
|
||||
id: group.groupId,
|
||||
name: group.desc,
|
||||
items: _buildGroupItems(group),
|
||||
customData: GroupData(
|
||||
group: group,
|
||||
fieldInfo: fieldController.getField(group.fieldId)!,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
boardController.addGroups(groups);
|
||||
.map((group) => initializeGroupData(group))
|
||||
.toList());
|
||||
|
||||
for (final group in groupsData) {
|
||||
final delegate = GroupControllerDelegateImpl(
|
||||
controller: boardController,
|
||||
fieldController: fieldController,
|
||||
onNewColumnItem: (groupId, row, index) {
|
||||
add(BoardEvent.didCreateRow(group, row, index));
|
||||
},
|
||||
);
|
||||
final controller = GroupController(
|
||||
databaseId: state.databaseId,
|
||||
group: group,
|
||||
delegate: delegate,
|
||||
);
|
||||
controller.startListening();
|
||||
for (final group in groups) {
|
||||
final controller = initializeGroupController(group);
|
||||
groupControllers[controller.group.groupId] = (controller);
|
||||
}
|
||||
}
|
||||
@ -245,11 +222,15 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
},
|
||||
onDeletedGroup: (groupIds) {
|
||||
if (isClosed) return;
|
||||
//
|
||||
boardController.removeGroups(groupIds);
|
||||
},
|
||||
onInsertedGroup: (insertedGroups) {
|
||||
onInsertedGroup: (insertedGroup) {
|
||||
if (isClosed) return;
|
||||
//
|
||||
final group = insertedGroup.group;
|
||||
final newGroup = initializeGroupData(group);
|
||||
final controller = initializeGroupController(group);
|
||||
groupControllers[controller.group.groupId] = (controller);
|
||||
boardController.addGroup(newGroup);
|
||||
},
|
||||
onUpdatedGroup: (updatedGroups) {
|
||||
if (isClosed) return;
|
||||
@ -294,6 +275,35 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
GroupController initializeGroupController(GroupPB group) {
|
||||
final delegate = GroupControllerDelegateImpl(
|
||||
controller: boardController,
|
||||
fieldController: fieldController,
|
||||
onNewColumnItem: (groupId, row, index) {
|
||||
add(BoardEvent.didCreateRow(group, row, index));
|
||||
},
|
||||
);
|
||||
final controller = GroupController(
|
||||
databaseId: state.databaseId,
|
||||
group: group,
|
||||
delegate: delegate,
|
||||
);
|
||||
controller.startListening();
|
||||
return controller;
|
||||
}
|
||||
|
||||
AppFlowyGroupData initializeGroupData(GroupPB group) {
|
||||
return AppFlowyGroupData(
|
||||
id: group.groupId,
|
||||
name: group.desc,
|
||||
items: _buildGroupItems(group),
|
||||
customData: GroupData(
|
||||
group: group,
|
||||
fieldInfo: fieldController.getField(group.fieldId)!,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -17,7 +17,7 @@ typedef OnGridChanged = void Function(DatabasePB);
|
||||
typedef DidLoadGroups = void Function(List<GroupPB>);
|
||||
typedef OnUpdatedGroup = void Function(List<GroupPB>);
|
||||
typedef OnDeletedGroup = void Function(List<String>);
|
||||
typedef OnInsertedGroup = void Function(List<InsertedGroupPB>);
|
||||
typedef OnInsertedGroup = void Function(InsertedGroupPB);
|
||||
typedef OnResetGroups = void Function(List<GroupPB>);
|
||||
|
||||
typedef OnRowsChanged = void Function(
|
||||
@ -90,8 +90,8 @@ class BoardDataController {
|
||||
onDeletedGroup.call(changeset.deletedGroups);
|
||||
}
|
||||
|
||||
if (changeset.insertedGroups.isNotEmpty) {
|
||||
onInsertedGroup.call(changeset.insertedGroups);
|
||||
for (final insertedGroup in changeset.insertedGroups) {
|
||||
onInsertedGroup.call(insertedGroup);
|
||||
}
|
||||
},
|
||||
(e) => _onError?.call(e),
|
||||
|
@ -46,7 +46,7 @@ class BoardListener {
|
||||
case DatabaseNotification.DidGroupByNewField:
|
||||
result.fold(
|
||||
(payload) => _groupByNewFieldNotifier?.value =
|
||||
left(GroupViewChangesetPB.fromBuffer(payload).newGroups),
|
||||
left(GroupViewChangesetPB.fromBuffer(payload).initialGroups),
|
||||
(error) => _groupByNewFieldNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
@ -270,7 +270,7 @@ class GridCellController<T, D> extends Equatable {
|
||||
/// You can set [deduplicate] to true (default is false) to reduce the save operation.
|
||||
/// It's useful when you call this method when user editing the [TextField].
|
||||
/// The default debounce interval is 300 milliseconds.
|
||||
void saveCellData(
|
||||
Future<void> saveCellData(
|
||||
D data, {
|
||||
bool deduplicate = false,
|
||||
void Function(Option<FlowyError>)? onFinish,
|
||||
|
@ -14,13 +14,16 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
}) : super(URLCellEditorState.initial(cellController)) {
|
||||
on<URLCellEditorEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
await event.when(
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
updateText: (text) {
|
||||
cellController.saveCellData(text, deduplicate: true);
|
||||
emit(state.copyWith(content: text));
|
||||
updateText: (text) async {
|
||||
await cellController.saveCellData(text);
|
||||
emit(state.copyWith(
|
||||
content: text,
|
||||
isFinishEditing: true,
|
||||
));
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(content: cellData?.content ?? ""));
|
||||
@ -63,12 +66,14 @@ class URLCellEditorEvent with _$URLCellEditorEvent {
|
||||
class URLCellEditorState with _$URLCellEditorState {
|
||||
const factory URLCellEditorState({
|
||||
required String content,
|
||||
required bool isFinishEditing,
|
||||
}) = _URLCellEditorState;
|
||||
|
||||
factory URLCellEditorState.initial(GridURLCellController context) {
|
||||
final cellData = context.getCellData();
|
||||
return URLCellEditorState(
|
||||
content: cellData?.content ?? "",
|
||||
isFinishEditing: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,13 @@ import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class URLCellEditor extends StatefulWidget {
|
||||
final VoidCallback onExit;
|
||||
final GridURLCellController cellController;
|
||||
const URLCellEditor({required this.cellController, Key? key})
|
||||
: super(key: key);
|
||||
const URLCellEditor({
|
||||
required this.cellController,
|
||||
required this.onExit,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<URLCellEditor> createState() => _URLCellEditorState();
|
||||
@ -36,12 +40,17 @@ class _URLCellEditorState extends State<URLCellEditor> {
|
||||
if (_controller.text != state.content) {
|
||||
_controller.text = state.content;
|
||||
}
|
||||
|
||||
if (state.isFinishEditing) {
|
||||
widget.onExit();
|
||||
}
|
||||
},
|
||||
child: TextField(
|
||||
autofocus: true,
|
||||
controller: _controller,
|
||||
onChanged: (value) => focusChanged(),
|
||||
maxLines: null,
|
||||
onSubmitted: (value) => focusChanged(),
|
||||
onEditingComplete: () => focusChanged(),
|
||||
maxLines: 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
@ -57,11 +66,10 @@ class _URLCellEditorState extends State<URLCellEditor> {
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> focusChanged() async {
|
||||
void focusChanged() {
|
||||
if (mounted) {
|
||||
if (_cellBloc.isClosed == false &&
|
||||
_controller.text != _cellBloc.state.content) {
|
||||
@ -72,9 +80,13 @@ class _URLCellEditorState extends State<URLCellEditor> {
|
||||
}
|
||||
|
||||
class URLEditorPopover extends StatelessWidget {
|
||||
final VoidCallback onExit;
|
||||
final GridURLCellController cellController;
|
||||
const URLEditorPopover({required this.cellController, Key? key})
|
||||
: super(key: key);
|
||||
const URLEditorPopover({
|
||||
required this.cellController,
|
||||
required this.onExit,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -84,6 +96,7 @@ class URLEditorPopover extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: URLCellEditor(
|
||||
cellController: cellController,
|
||||
onExit: onExit,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -142,6 +142,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||
return URLEditorPopover(
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as GridURLCellController,
|
||||
onExit: () => _popoverController.close(),
|
||||
);
|
||||
},
|
||||
onClose: () {
|
||||
@ -219,6 +220,7 @@ class _EditURLAccessoryState extends State<_EditURLAccessory>
|
||||
return URLEditorPopover(
|
||||
cellController:
|
||||
widget.cellControllerBuilder.build() as GridURLCellController,
|
||||
onExit: () => _popoverController.close(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -1,10 +1,10 @@
|
||||
use flowy_core::{get_client_server_configuration, FlowySDK, FlowySDKConfig};
|
||||
use flowy_core::{get_client_server_configuration, AppFlowyCore, AppFlowyCoreConfig};
|
||||
|
||||
pub fn init_flowy_core() -> FlowySDK {
|
||||
pub fn init_flowy_core() -> AppFlowyCore {
|
||||
let data_path = tauri::api::path::data_dir().unwrap();
|
||||
let path = format!("{}/AppFlowy", data_path.to_str().unwrap());
|
||||
let server_config = get_client_server_configuration().unwrap();
|
||||
let config = FlowySDKConfig::new(&path, "AppFlowy".to_string(), server_config)
|
||||
let config = AppFlowyCoreConfig::new(&path, "AppFlowy".to_string(), server_config)
|
||||
.log_filter("trace", vec!["appflowy_tauri".to_string()]);
|
||||
FlowySDK::new(config)
|
||||
AppFlowyCore::new(config)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use flowy_core::FlowySDK;
|
||||
use flowy_core::AppFlowyCore;
|
||||
use lib_dispatch::prelude::{
|
||||
AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode,
|
||||
};
|
||||
@ -39,7 +39,7 @@ pub async fn invoke_request(
|
||||
app_handler: AppHandle<Wry>,
|
||||
) -> AFTauriResponse {
|
||||
let request: AFPluginRequest = request.into();
|
||||
let state: State<FlowySDK> = app_handler.state();
|
||||
let state: State<AppFlowyCore> = app_handler.state();
|
||||
let dispatcher = state.inner().dispatcher();
|
||||
let response = AFPluginDispatcher::async_send(dispatcher, request).await;
|
||||
response.into()
|
||||
|
@ -20,7 +20,7 @@ use parking_lot::RwLock;
|
||||
use std::{ffi::CStr, os::raw::c_char};
|
||||
|
||||
lazy_static! {
|
||||
static ref FLOWY_SDK: RwLock<Option<FlowySDK>> = RwLock::new(None);
|
||||
static ref APPFLOWY_CORE: RwLock<Option<AppFlowyCore>> = RwLock::new(None);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@ -30,8 +30,8 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
|
||||
|
||||
let server_config = get_client_server_configuration().unwrap();
|
||||
let log_crates = vec!["flowy-ffi".to_string()];
|
||||
let config = FlowySDKConfig::new(path, "appflowy".to_string(), server_config).log_filter("info", log_crates);
|
||||
*FLOWY_SDK.write() = Some(FlowySDK::new(config));
|
||||
let config = AppFlowyCoreConfig::new(path, "appflowy".to_string(), server_config).log_filter("info", log_crates);
|
||||
*APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config));
|
||||
|
||||
0
|
||||
}
|
||||
@ -46,7 +46,7 @@ pub extern "C" fn async_event(port: i64, input: *const u8, len: usize) {
|
||||
port
|
||||
);
|
||||
|
||||
let dispatcher = match FLOWY_SDK.read().as_ref() {
|
||||
let dispatcher = match APPFLOWY_CORE.read().as_ref() {
|
||||
None => {
|
||||
log::error!("sdk not init yet.");
|
||||
return;
|
||||
@ -64,7 +64,7 @@ pub extern "C" fn sync_event(input: *const u8, len: usize) -> *const u8 {
|
||||
let request: AFPluginRequest = FFIRequest::from_u8_pointer(input, len).into();
|
||||
log::trace!("[FFI]: {} Sync Event: {:?}", &request.id, &request.event,);
|
||||
|
||||
let dispatcher = match FLOWY_SDK.read().as_ref() {
|
||||
let dispatcher = match APPFLOWY_CORE.read().as_ref() {
|
||||
None => {
|
||||
log::error!("sdk not init yet.");
|
||||
return forget_rust(Vec::default());
|
||||
|
@ -34,31 +34,31 @@ use user_model::UserProfile;
|
||||
static INIT_LOG: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FlowySDKConfig {
|
||||
/// Different `FlowySDK` instance should have different name
|
||||
pub struct AppFlowyCoreConfig {
|
||||
/// Different `AppFlowyCoreConfig` instance should have different name
|
||||
name: String,
|
||||
/// Panics if the `root` path is not existing
|
||||
root: String,
|
||||
storage_path: String,
|
||||
log_filter: String,
|
||||
server_config: ClientServerConfiguration,
|
||||
pub document: DocumentConfig,
|
||||
}
|
||||
|
||||
impl fmt::Debug for FlowySDKConfig {
|
||||
impl fmt::Debug for AppFlowyCoreConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("FlowySDKConfig")
|
||||
.field("root", &self.root)
|
||||
f.debug_struct("AppFlowyCoreConfig")
|
||||
.field("storage_path", &self.storage_path)
|
||||
.field("server-config", &self.server_config)
|
||||
.field("document-config", &self.document)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowySDKConfig {
|
||||
impl AppFlowyCoreConfig {
|
||||
pub fn new(root: &str, name: String, server_config: ClientServerConfiguration) -> Self {
|
||||
FlowySDKConfig {
|
||||
AppFlowyCoreConfig {
|
||||
name,
|
||||
root: root.to_owned(),
|
||||
storage_path: root.to_owned(),
|
||||
log_filter: create_log_filter("info".to_owned(), vec![]),
|
||||
server_config,
|
||||
document: DocumentConfig::default(),
|
||||
@ -106,9 +106,9 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FlowySDK {
|
||||
pub struct AppFlowyCore {
|
||||
#[allow(dead_code)]
|
||||
pub config: FlowySDKConfig,
|
||||
pub config: AppFlowyCoreConfig,
|
||||
pub user_session: Arc<UserSession>,
|
||||
pub document_manager: Arc<DocumentManager>,
|
||||
pub folder_manager: Arc<FolderManager>,
|
||||
@ -119,10 +119,10 @@ pub struct FlowySDK {
|
||||
pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
|
||||
}
|
||||
|
||||
impl FlowySDK {
|
||||
pub fn new(config: FlowySDKConfig) -> Self {
|
||||
impl AppFlowyCore {
|
||||
pub fn new(config: AppFlowyCoreConfig) -> Self {
|
||||
init_log(&config);
|
||||
init_kv(&config.root);
|
||||
init_kv(&config.storage_path);
|
||||
tracing::debug!("🔥 {:?}", config);
|
||||
let runtime = tokio_default_runtime().unwrap();
|
||||
let task_scheduler = TaskDispatcher::new(Duration::from_secs(2));
|
||||
@ -257,22 +257,22 @@ fn init_kv(root: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
fn init_log(config: &FlowySDKConfig) {
|
||||
fn init_log(config: &AppFlowyCoreConfig) {
|
||||
if !INIT_LOG.load(Ordering::SeqCst) {
|
||||
INIT_LOG.store(true, Ordering::SeqCst);
|
||||
|
||||
let _ = lib_log::Builder::new("AppFlowy-Client", &config.root)
|
||||
let _ = lib_log::Builder::new("AppFlowy-Client", &config.storage_path)
|
||||
.env_filter(&config.log_filter)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
fn mk_user_session(
|
||||
config: &FlowySDKConfig,
|
||||
config: &AppFlowyCoreConfig,
|
||||
local_server: &Option<Arc<LocalServer>>,
|
||||
server_config: &ClientServerConfiguration,
|
||||
) -> Arc<UserSession> {
|
||||
let user_config = UserSessionConfig::new(&config.name, &config.root);
|
||||
let user_config = UserSessionConfig::new(&config.name, &config.storage_path);
|
||||
let cloud_service = UserDepsResolver::resolve(local_server, server_config);
|
||||
Arc::new(UserSession::new(user_config, cloud_service))
|
||||
}
|
||||
@ -282,7 +282,7 @@ struct UserStatusListener {
|
||||
folder_manager: Arc<FolderManager>,
|
||||
grid_manager: Arc<DatabaseManager>,
|
||||
ws_conn: Arc<FlowyWebSocketConnect>,
|
||||
config: FlowySDKConfig,
|
||||
config: AppFlowyCoreConfig,
|
||||
}
|
||||
|
||||
impl UserStatusListener {
|
||||
|
@ -135,7 +135,7 @@ pub struct GroupViewChangesetPB {
|
||||
pub inserted_groups: Vec<InsertedGroupPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub new_groups: Vec<GroupPB>,
|
||||
pub initial_groups: Vec<GroupPB>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub deleted_groups: Vec<String>,
|
||||
@ -146,7 +146,7 @@ pub struct GroupViewChangesetPB {
|
||||
|
||||
impl GroupViewChangesetPB {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.new_groups.is_empty()
|
||||
self.initial_groups.is_empty()
|
||||
&& self.inserted_groups.is_empty()
|
||||
&& self.deleted_groups.is_empty()
|
||||
&& self.update_groups.is_empty()
|
||||
|
@ -76,7 +76,7 @@ pub fn apply_cell_data_changeset<C: ToCellChangesetString, T: AsRef<FieldRevisio
|
||||
Ok(TypeCellData::new(cell_str, field_type).to_json())
|
||||
}
|
||||
|
||||
pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debug>(
|
||||
pub fn get_type_cell_protobuf<T: TryInto<TypeCellData, Error = FlowyError> + Debug>(
|
||||
data: T,
|
||||
field_rev: &FieldRevision,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
@ -85,7 +85,13 @@ pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debu
|
||||
match data.try_into() {
|
||||
Ok(type_cell_data) => {
|
||||
let TypeCellData { cell_str, field_type } = type_cell_data;
|
||||
match try_decode_cell_str(cell_str, &field_type, &to_field_type, field_rev, cell_data_cache) {
|
||||
match try_decode_cell_str_to_cell_protobuf(
|
||||
cell_str,
|
||||
&field_type,
|
||||
&to_field_type,
|
||||
field_rev,
|
||||
cell_data_cache,
|
||||
) {
|
||||
Ok(cell_bytes) => (field_type, cell_bytes),
|
||||
Err(e) => {
|
||||
tracing::error!("Decode cell data failed, {:?}", e);
|
||||
@ -97,12 +103,30 @@ pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debu
|
||||
// It's okay to ignore this error, because it's okay that the current cell can't
|
||||
// display the existing cell data. For example, the UI of the text cell will be blank if
|
||||
// the type of the data of cell is Number.
|
||||
|
||||
(to_field_type, CellProtobufBlob::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type_cell_data<CellData, Output>(
|
||||
data: CellData,
|
||||
field_rev: &FieldRevision,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
) -> Option<Output>
|
||||
where
|
||||
CellData: TryInto<TypeCellData, Error = FlowyError> + Debug,
|
||||
Output: Default + 'static,
|
||||
{
|
||||
let to_field_type = field_rev.ty.into();
|
||||
match data.try_into() {
|
||||
Ok(type_cell_data) => {
|
||||
let TypeCellData { cell_str, field_type } = type_cell_data;
|
||||
try_decode_cell_str_to_cell_data(cell_str, &field_type, &to_field_type, field_rev, cell_data_cache)
|
||||
}
|
||||
Err(_err) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode the opaque cell data from one field type to another using the corresponding `TypeOption`
|
||||
///
|
||||
/// The cell data might become an empty string depends on the to_field_type's `TypeOption`
|
||||
@ -120,7 +144,7 @@ pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debu
|
||||
///
|
||||
/// returns: CellBytes
|
||||
///
|
||||
pub fn try_decode_cell_str(
|
||||
pub fn try_decode_cell_str_to_cell_protobuf(
|
||||
cell_str: String,
|
||||
from_field_type: &FieldType,
|
||||
to_field_type: &FieldType,
|
||||
@ -135,6 +159,20 @@ pub fn try_decode_cell_str(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_decode_cell_str_to_cell_data<T: Default + 'static>(
|
||||
cell_str: String,
|
||||
from_field_type: &FieldType,
|
||||
to_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
) -> Option<T> {
|
||||
let handler = TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache)
|
||||
.get_type_option_cell_data_handler(to_field_type)?;
|
||||
handler
|
||||
.get_cell_data(cell_str, from_field_type, field_rev)
|
||||
.ok()?
|
||||
.unbox_or_none::<T>()
|
||||
}
|
||||
/// Returns a string that represents the current field_type's cell data.
|
||||
/// For example, The string of the Multi-Select cell will be a list of the option's name
|
||||
/// separated by a comma.
|
||||
|
@ -497,7 +497,7 @@ impl BoxCellData {
|
||||
}
|
||||
}
|
||||
|
||||
fn unbox_or_none<T>(self) -> Option<T>
|
||||
pub(crate) fn unbox_or_none<T>(self) -> Option<T>
|
||||
where
|
||||
T: Default + 'static,
|
||||
{
|
||||
|
@ -49,6 +49,15 @@ impl URLCellData {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<URLCellDataPB> for URLCellData {
|
||||
fn from(data: URLCellDataPB) -> Self {
|
||||
Self {
|
||||
url: data.url,
|
||||
content: data.content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for URLCellData {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.url
|
||||
|
@ -4,7 +4,7 @@ use crate::manager::DatabaseUser;
|
||||
use crate::notification::{send_notification, DatabaseNotification};
|
||||
use crate::services::block_manager::DatabaseBlockManager;
|
||||
use crate::services::cell::{
|
||||
apply_cell_data_changeset, decode_type_cell_data, stringify_cell_data, AnyTypeCache, AtomicCellDataCache,
|
||||
apply_cell_data_changeset, get_type_cell_protobuf, stringify_cell_data, AnyTypeCache, AtomicCellDataCache,
|
||||
CellProtobufBlob, ToCellChangesetString, TypeCellData,
|
||||
};
|
||||
use crate::services::field::{
|
||||
@ -392,8 +392,9 @@ impl DatabaseRevisionEditor {
|
||||
|
||||
pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> {
|
||||
let row_id = changeset.row_id.clone();
|
||||
let old_row = self.get_row_rev(&row_id).await?;
|
||||
self.block_manager.update_row(changeset).await?;
|
||||
self.view_manager.did_update_cell(&row_id).await;
|
||||
self.view_manager.did_update_row(old_row, &row_id).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -440,7 +441,7 @@ impl DatabaseRevisionEditor {
|
||||
|
||||
/// Returns the cell data that encoded in protobuf.
|
||||
pub async fn get_cell(&self, params: &CellPathParams) -> Option<CellPB> {
|
||||
let (field_type, cell_bytes) = self.decode_cell_data_from(params).await?;
|
||||
let (field_type, cell_bytes) = self.get_type_cell_protobuf(params).await?;
|
||||
Some(CellPB::new(
|
||||
¶ms.field_id,
|
||||
¶ms.row_id,
|
||||
@ -473,15 +474,15 @@ impl DatabaseRevisionEditor {
|
||||
}
|
||||
|
||||
pub async fn get_cell_protobuf(&self, params: &CellPathParams) -> Option<CellProtobufBlob> {
|
||||
let (_, cell_data) = self.decode_cell_data_from(params).await?;
|
||||
let (_, cell_data) = self.get_type_cell_protobuf(params).await?;
|
||||
Some(cell_data)
|
||||
}
|
||||
|
||||
async fn decode_cell_data_from(&self, params: &CellPathParams) -> Option<(FieldType, CellProtobufBlob)> {
|
||||
async fn get_type_cell_protobuf(&self, params: &CellPathParams) -> Option<(FieldType, CellProtobufBlob)> {
|
||||
let field_rev = self.get_field_rev(¶ms.field_id).await?;
|
||||
let (_, row_rev) = self.block_manager.get_row_rev(¶ms.row_id).await.ok()??;
|
||||
let cell_rev = row_rev.cells.get(¶ms.field_id)?.clone();
|
||||
Some(decode_type_cell_data(
|
||||
Some(get_type_cell_protobuf(
|
||||
cell_rev.type_cell_data,
|
||||
&field_rev,
|
||||
Some(self.cell_data_cache.clone()),
|
||||
@ -513,11 +514,12 @@ impl DatabaseRevisionEditor {
|
||||
) -> FlowyResult<()> {
|
||||
match self.database_pad.read().await.get_field_rev(field_id) {
|
||||
None => {
|
||||
let msg = format!("Field:{} not found", &field_id);
|
||||
let msg = format!("Field with id:{} not found", &field_id);
|
||||
Err(FlowyError::internal().context(msg))
|
||||
}
|
||||
Some((_, field_rev)) => {
|
||||
tracing::trace!("Cell changeset: id:{} / value:{:?}", &field_id, cell_changeset);
|
||||
let old_row_rev = self.get_row_rev(row_id).await?.clone();
|
||||
let cell_rev = self.get_cell_rev(row_id, field_id).await?;
|
||||
// Update the changeset.data property with the return value.
|
||||
let type_cell_data =
|
||||
@ -529,7 +531,7 @@ impl DatabaseRevisionEditor {
|
||||
type_cell_data,
|
||||
};
|
||||
self.block_manager.update_cell(cell_changeset).await?;
|
||||
self.view_manager.did_update_cell(row_id).await;
|
||||
self.view_manager.did_update_row(old_row_rev, row_id).await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB};
|
||||
use crate::entities::{GroupPB, GroupRowsNotificationPB, GroupViewChangesetPB, InsertedGroupPB};
|
||||
use crate::services::cell::DecodedCellData;
|
||||
use crate::services::group::controller::MoveGroupRowContext;
|
||||
use crate::services::group::Group;
|
||||
@ -10,42 +10,53 @@ use std::sync::Arc;
|
||||
///
|
||||
/// For example, the `CheckboxGroupController` implements this trait to provide custom behavior.
|
||||
///
|
||||
pub trait GroupControllerCustomActions: Send + Sync {
|
||||
type CellDataType: DecodedCellData;
|
||||
/// Returns the a value of the cell, default value is None
|
||||
pub trait GroupCustomize: Send + Sync {
|
||||
type CellData: DecodedCellData;
|
||||
/// Returns the a value of the cell if the cell data is not exist.
|
||||
/// The default value is `None`
|
||||
///
|
||||
/// Determine which group the row is placed in based on the data of the cell. If the cell data
|
||||
/// is None. The row will be put in to the `No status` group
|
||||
///
|
||||
fn default_cell_rev(&self) -> Option<CellRevision> {
|
||||
fn placeholder_cell(&self) -> Option<CellRevision> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns a bool value to determine whether the group should contain this cell or not.
|
||||
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
|
||||
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool;
|
||||
|
||||
fn create_or_delete_group_when_cell_changed(
|
||||
&mut self,
|
||||
_row_rev: &RowRevision,
|
||||
_old_cell_data: Option<&Self::CellData>,
|
||||
_cell_data: &Self::CellData,
|
||||
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
|
||||
Ok((None, None))
|
||||
}
|
||||
|
||||
/// Adds or removes a row if the cell data match the group filter.
|
||||
/// It gets called after editing the cell or row
|
||||
///
|
||||
fn add_or_remove_row_in_groups_if_match(
|
||||
fn add_or_remove_row_when_cell_changed(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellDataType,
|
||||
cell_data: &Self::CellData,
|
||||
) -> Vec<GroupRowsNotificationPB>;
|
||||
|
||||
/// Deletes the row from the group
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB>;
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB>;
|
||||
|
||||
/// Move row from one group to another
|
||||
fn move_row(
|
||||
&mut self,
|
||||
cell_data: &Self::CellDataType,
|
||||
context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB>;
|
||||
fn move_row(&mut self, cell_data: &Self::CellData, context: MoveGroupRowContext) -> Vec<GroupRowsNotificationPB>;
|
||||
|
||||
/// Returns None if there is no need to delete the group when corresponding row get removed
|
||||
fn delete_group_when_move_row(&mut self, _row_rev: &RowRevision, _cell_data: &Self::CellData) -> Option<GroupPB> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the shared actions any group controller can perform.
|
||||
pub trait GroupControllerSharedActions: Send + Sync {
|
||||
pub trait GroupControllerActions: Send + Sync {
|
||||
/// The field that is used for grouping the rows
|
||||
fn field_id(&self) -> &str;
|
||||
|
||||
@ -64,20 +75,34 @@ pub trait GroupControllerSharedActions: Send + Sync {
|
||||
/// Insert/Remove the row to the group if the corresponding cell data is changed
|
||||
fn did_update_group_row(
|
||||
&mut self,
|
||||
old_row_rev: &Option<Arc<RowRevision>>,
|
||||
row_rev: &RowRevision,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
|
||||
) -> FlowyResult<DidUpdateGroupRowResult>;
|
||||
|
||||
/// Remove the row from the group if the row gets deleted
|
||||
fn did_delete_delete_row(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
|
||||
) -> FlowyResult<DidMoveGroupRowResult>;
|
||||
|
||||
/// Move the row from one group to another group
|
||||
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
|
||||
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<DidMoveGroupRowResult>;
|
||||
|
||||
/// Update the group if the corresponding field is changed
|
||||
fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DidUpdateGroupRowResult {
|
||||
pub(crate) inserted_group: Option<InsertedGroupPB>,
|
||||
pub(crate) deleted_group: Option<GroupPB>,
|
||||
pub(crate) row_changesets: Vec<GroupRowsNotificationPB>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DidMoveGroupRowResult {
|
||||
pub(crate) deleted_group: Option<GroupPB>,
|
||||
pub(crate) row_changesets: Vec<GroupRowsNotificationPB>,
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{GroupPB, GroupViewChangesetPB};
|
||||
use crate::entities::{GroupPB, GroupViewChangesetPB, InsertedGroupPB};
|
||||
use crate::services::field::RowSingleCellData;
|
||||
use crate::services::group::{default_group_configuration, GeneratedGroupContext, Group};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -101,7 +101,7 @@ where
|
||||
|
||||
/// Returns the no `status` group
|
||||
///
|
||||
/// We take the `id` of the `field` as the default group id
|
||||
/// We take the `id` of the `field` as the no status group id
|
||||
pub(crate) fn get_no_status_group(&self) -> Option<&Group> {
|
||||
self.groups_map.get(&self.field_rev.id)
|
||||
}
|
||||
@ -140,6 +140,37 @@ where
|
||||
each(group);
|
||||
});
|
||||
}
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub(crate) fn add_new_group(&mut self, group_rev: GroupRevision) -> FlowyResult<InsertedGroupPB> {
|
||||
let group = Group::new(
|
||||
group_rev.id.clone(),
|
||||
self.field_rev.id.clone(),
|
||||
group_rev.name.clone(),
|
||||
group_rev.id.clone(),
|
||||
);
|
||||
self.groups_map.insert(group_rev.id.clone(), group);
|
||||
let (index, group) = self.get_group(&group_rev.id).unwrap();
|
||||
let insert_group = InsertedGroupPB {
|
||||
group: GroupPB::from(group.clone()),
|
||||
index: index as i32,
|
||||
};
|
||||
self.mut_configuration(|configuration| {
|
||||
configuration.groups.push(group_rev);
|
||||
true
|
||||
})?;
|
||||
|
||||
Ok(insert_group)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) fn delete_group(&mut self, deleted_group_id: &str) -> FlowyResult<()> {
|
||||
self.groups_map.remove(deleted_group_id);
|
||||
self.mut_configuration(|configuration| {
|
||||
configuration.groups.retain(|group| group.id != deleted_group_id);
|
||||
true
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn move_group(&mut self, from_id: &str, to_id: &str) -> FlowyResult<()> {
|
||||
let from_index = self.groups_map.get_index_of(from_id);
|
||||
@ -211,18 +242,12 @@ where
|
||||
old_groups.clear();
|
||||
}
|
||||
|
||||
// The `No status` group index is initialized to 0
|
||||
if let Some(no_status_group) = no_status_group {
|
||||
old_groups.insert(0, no_status_group);
|
||||
}
|
||||
|
||||
// The `all_group_revs` is the combination of the new groups and old groups
|
||||
let MergeGroupResult {
|
||||
mut all_group_revs,
|
||||
new_group_revs,
|
||||
updated_group_revs: _,
|
||||
deleted_group_revs,
|
||||
} = merge_groups(old_groups, new_groups);
|
||||
} = merge_groups(no_status_group, old_groups, new_groups);
|
||||
|
||||
let deleted_group_ids = deleted_group_revs
|
||||
.into_iter()
|
||||
@ -231,13 +256,10 @@ where
|
||||
|
||||
self.mut_configuration(|configuration| {
|
||||
let mut is_changed = !deleted_group_ids.is_empty();
|
||||
|
||||
// Remove the groups
|
||||
if !deleted_group_ids.is_empty() {
|
||||
configuration
|
||||
.groups
|
||||
.retain(|group| !deleted_group_ids.contains(&group.id));
|
||||
}
|
||||
configuration
|
||||
.groups
|
||||
.retain(|group| !deleted_group_ids.contains(&group.id));
|
||||
|
||||
// Update/Insert new groups
|
||||
for group_rev in &mut all_group_revs {
|
||||
@ -276,7 +298,7 @@ where
|
||||
self.groups_map.insert(group.id.clone(), group);
|
||||
});
|
||||
|
||||
let new_groups = new_group_revs
|
||||
let initial_groups = new_group_revs
|
||||
.into_iter()
|
||||
.flat_map(|group_rev| {
|
||||
let filter_content = filter_content_map.get(&group_rev.id)?;
|
||||
@ -292,7 +314,7 @@ where
|
||||
|
||||
let changeset = GroupViewChangesetPB {
|
||||
view_id: self.view_id.clone(),
|
||||
new_groups,
|
||||
initial_groups,
|
||||
deleted_groups: deleted_group_ids,
|
||||
update_groups: vec![],
|
||||
inserted_groups: vec![],
|
||||
@ -366,7 +388,11 @@ where
|
||||
|
||||
/// Merge the new groups into old groups while keeping the order in the old groups
|
||||
///
|
||||
fn merge_groups(old_groups: Vec<GroupRevision>, new_groups: Vec<GroupRevision>) -> MergeGroupResult {
|
||||
fn merge_groups(
|
||||
no_status_group: Option<GroupRevision>,
|
||||
old_groups: Vec<GroupRevision>,
|
||||
new_groups: Vec<GroupRevision>,
|
||||
) -> MergeGroupResult {
|
||||
let mut merge_result = MergeGroupResult::new();
|
||||
// group_map is a helper map is used to filter out the new groups.
|
||||
let mut new_group_map: IndexMap<String, GroupRevision> = IndexMap::new();
|
||||
@ -378,11 +404,8 @@ fn merge_groups(old_groups: Vec<GroupRevision>, new_groups: Vec<GroupRevision>)
|
||||
for old in old_groups {
|
||||
if let Some(new) = new_group_map.remove(&old.id) {
|
||||
merge_result.all_group_revs.push(new.clone());
|
||||
if is_group_changed(&new, &old) {
|
||||
merge_result.updated_group_revs.push(new);
|
||||
}
|
||||
} else {
|
||||
merge_result.all_group_revs.push(old);
|
||||
merge_result.deleted_group_revs.push(old);
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,6 +415,11 @@ fn merge_groups(old_groups: Vec<GroupRevision>, new_groups: Vec<GroupRevision>)
|
||||
merge_result.all_group_revs.push(group.clone());
|
||||
merge_result.new_group_revs.push(group);
|
||||
}
|
||||
|
||||
// The `No status` group index is initialized to 0
|
||||
if let Some(no_status_group) = no_status_group {
|
||||
merge_result.all_group_revs.insert(0, no_status_group);
|
||||
}
|
||||
merge_result
|
||||
}
|
||||
|
||||
@ -399,7 +427,6 @@ fn is_group_changed(new: &GroupRevision, old: &GroupRevision) -> bool {
|
||||
if new.name != old.name {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
@ -407,7 +434,6 @@ struct MergeGroupResult {
|
||||
// Contains the new groups and the updated groups
|
||||
all_group_revs: Vec<GroupRevision>,
|
||||
new_group_revs: Vec<GroupRevision>,
|
||||
updated_group_revs: Vec<GroupRevision>,
|
||||
deleted_group_revs: Vec<GroupRevision>,
|
||||
}
|
||||
|
||||
@ -416,7 +442,6 @@ impl MergeGroupResult {
|
||||
Self {
|
||||
all_group_revs: vec![],
|
||||
new_group_revs: vec![],
|
||||
updated_group_revs: vec![],
|
||||
deleted_group_revs: vec![],
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, InsertedRowPB, RowPB};
|
||||
use crate::services::cell::{decode_type_cell_data, CellProtobufBlobParser, DecodedCellData};
|
||||
use crate::services::group::action::{GroupControllerCustomActions, GroupControllerSharedActions};
|
||||
use crate::services::cell::{get_type_cell_protobuf, CellProtobufBlobParser, DecodedCellData};
|
||||
|
||||
use crate::services::group::action::{
|
||||
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions, GroupCustomize,
|
||||
};
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
use crate::services::group::entities::Group;
|
||||
use flowy_error::FlowyResult;
|
||||
use grid_model::{
|
||||
FieldRevision, GroupConfigurationContentSerde, GroupRevision, RowChangeset, RowRevision, TypeOptionDataDeserializer,
|
||||
CellRevision, FieldRevision, GroupConfigurationContentSerde, GroupRevision, RowChangeset, RowRevision,
|
||||
TypeOptionDataDeserializer,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
@ -18,7 +22,7 @@ use std::sync::Arc;
|
||||
/// If the [FieldType] doesn't implement its group controller, then the [DefaultGroupController] will
|
||||
/// be used.
|
||||
///
|
||||
pub trait GroupController: GroupControllerSharedActions + Send + Sync {
|
||||
pub trait GroupController: GroupControllerActions + Send + Sync {
|
||||
fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str);
|
||||
fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str);
|
||||
}
|
||||
@ -86,12 +90,12 @@ where
|
||||
|
||||
// https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn update_default_group(
|
||||
fn update_no_status_group(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
other_group_changesets: &[GroupRowsNotificationPB],
|
||||
) -> Option<GroupRowsNotificationPB> {
|
||||
let default_group = self.group_ctx.get_mut_no_status_group()?;
|
||||
let no_status_group = self.group_ctx.get_mut_no_status_group()?;
|
||||
|
||||
// [other_group_inserted_row] contains all the inserted rows except the default group.
|
||||
let other_group_inserted_row = other_group_changesets
|
||||
@ -100,7 +104,7 @@ where
|
||||
.collect::<Vec<&InsertedRowPB>>();
|
||||
|
||||
// Calculate the inserted_rows of the default_group
|
||||
let default_group_inserted_row = other_group_changesets
|
||||
let no_status_group_rows = other_group_changesets
|
||||
.iter()
|
||||
.flat_map(|changeset| &changeset.deleted_rows)
|
||||
.cloned()
|
||||
@ -113,10 +117,10 @@ where
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let mut changeset = GroupRowsNotificationPB::new(default_group.id.clone());
|
||||
if !default_group_inserted_row.is_empty() {
|
||||
let mut changeset = GroupRowsNotificationPB::new(no_status_group.id.clone());
|
||||
if !no_status_group_rows.is_empty() {
|
||||
changeset.inserted_rows.push(InsertedRowPB::new(row_rev.into()));
|
||||
default_group.add_row(row_rev.into());
|
||||
no_status_group.add_row(row_rev.into());
|
||||
}
|
||||
|
||||
// [other_group_delete_rows] contains all the deleted rows except the default group.
|
||||
@ -138,7 +142,7 @@ where
|
||||
.collect::<Vec<&InsertedRowPB>>();
|
||||
|
||||
let mut deleted_row_ids = vec![];
|
||||
for row in &default_group.rows {
|
||||
for row in &no_status_group.rows {
|
||||
if default_group_deleted_rows
|
||||
.iter()
|
||||
.any(|deleted_row| deleted_row.row.id == row.id)
|
||||
@ -146,20 +150,20 @@ where
|
||||
deleted_row_ids.push(row.id.clone());
|
||||
}
|
||||
}
|
||||
default_group.rows.retain(|row| !deleted_row_ids.contains(&row.id));
|
||||
no_status_group.rows.retain(|row| !deleted_row_ids.contains(&row.id));
|
||||
changeset.deleted_rows.extend(deleted_row_ids);
|
||||
Some(changeset)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, T, G, P> GroupControllerSharedActions for GenericGroupController<C, T, G, P>
|
||||
impl<C, T, G, P> GroupControllerActions for GenericGroupController<C, T, G, P>
|
||||
where
|
||||
P: CellProtobufBlobParser,
|
||||
C: GroupConfigurationContentSerde,
|
||||
T: TypeOptionDataDeserializer,
|
||||
G: GroupGenerator<Context = GroupContext<C>, TypeOptionType = T>,
|
||||
|
||||
Self: GroupControllerCustomActions<CellDataType = P::Object>,
|
||||
Self: GroupCustomize<CellData = P::Object>,
|
||||
{
|
||||
fn field_id(&self) -> &str {
|
||||
&self.field_id
|
||||
@ -178,13 +182,13 @@ where
|
||||
fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<()> {
|
||||
for row_rev in row_revs {
|
||||
let cell_rev = match row_rev.cells.get(&self.field_id) {
|
||||
None => self.default_cell_rev(),
|
||||
None => self.placeholder_cell(),
|
||||
Some(cell_rev) => Some(cell_rev.clone()),
|
||||
};
|
||||
|
||||
if let Some(cell_rev) = cell_rev {
|
||||
let mut grouped_rows: Vec<GroupedRow> = vec![];
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, field_rev, None).1;
|
||||
let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data, field_rev, None).1;
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
for group in self.group_ctx.groups() {
|
||||
if self.can_group(&group.filter_content, &cell_data) {
|
||||
@ -206,7 +210,7 @@ where
|
||||
}
|
||||
match self.group_ctx.get_mut_no_status_group() {
|
||||
None => {}
|
||||
Some(default_group) => default_group.add_row(row_rev.into()),
|
||||
Some(no_status_group) => no_status_group.add_row(row_rev.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,73 +224,99 @@ where
|
||||
|
||||
fn did_update_group_row(
|
||||
&mut self,
|
||||
old_row_rev: &Option<Arc<RowRevision>>,
|
||||
row_rev: &RowRevision,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev, None).1;
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
let mut changesets = self.add_or_remove_row_in_groups_if_match(row_rev, &cell_data);
|
||||
) -> FlowyResult<DidUpdateGroupRowResult> {
|
||||
// let cell_data = row_rev.cells.get(&self.field_id).and_then(|cell_rev| {
|
||||
// let cell_data: Option<P> = get_type_cell_data(cell_rev, field_rev, None);
|
||||
// cell_data
|
||||
// });
|
||||
let mut result = DidUpdateGroupRowResult {
|
||||
inserted_group: None,
|
||||
deleted_group: None,
|
||||
row_changesets: vec![],
|
||||
};
|
||||
|
||||
if let Some(default_group_changeset) = self.update_default_group(row_rev, &changesets) {
|
||||
tracing::trace!("default_group_changeset: {}", default_group_changeset);
|
||||
if !default_group_changeset.is_empty() {
|
||||
changesets.push(default_group_changeset);
|
||||
if let Some(cell_data) = get_cell_data_from_row_rev::<P>(Some(row_rev), field_rev) {
|
||||
let old_row_rev = old_row_rev.as_ref().map(|old| old.as_ref());
|
||||
let old_cell_data = get_cell_data_from_row_rev::<P>(old_row_rev, field_rev);
|
||||
if let Ok((insert, delete)) =
|
||||
self.create_or_delete_group_when_cell_changed(row_rev, old_cell_data.as_ref(), &cell_data)
|
||||
{
|
||||
result.inserted_group = insert;
|
||||
result.deleted_group = delete;
|
||||
}
|
||||
|
||||
let mut changesets = self.add_or_remove_row_when_cell_changed(row_rev, &cell_data);
|
||||
if let Some(changeset) = self.update_no_status_group(row_rev, &changesets) {
|
||||
if !changeset.is_empty() {
|
||||
changesets.push(changeset);
|
||||
}
|
||||
}
|
||||
Ok(changesets)
|
||||
} else {
|
||||
Ok(vec![])
|
||||
result.row_changesets = changesets;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn did_delete_delete_row(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
) -> FlowyResult<DidMoveGroupRowResult> {
|
||||
// if the cell_rev is none, then the row must in the default group.
|
||||
let mut result = DidMoveGroupRowResult {
|
||||
deleted_group: None,
|
||||
row_changesets: vec![],
|
||||
};
|
||||
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev, None).1;
|
||||
let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data.clone(), field_rev, None).1;
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
if !cell_data.is_empty() {
|
||||
tracing::error!("did_delete_delete_row {:?}", cell_rev.type_cell_data);
|
||||
return Ok(self.delete_row(row_rev, &cell_data));
|
||||
result.row_changesets = self.delete_row(row_rev, &cell_data);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
match self.group_ctx.get_no_status_group() {
|
||||
None => {
|
||||
tracing::error!("Unexpected None value. It should have the no status group");
|
||||
Ok(vec![])
|
||||
}
|
||||
Some(no_status_group) => {
|
||||
if !no_status_group.contains_row(&row_rev.id) {
|
||||
tracing::error!("The row: {} should be in the no status group", row_rev.id);
|
||||
}
|
||||
Ok(vec![GroupRowsNotificationPB::delete(
|
||||
result.row_changesets = vec![GroupRowsNotificationPB::delete(
|
||||
no_status_group.id.clone(),
|
||||
vec![row_rev.id.clone()],
|
||||
)])
|
||||
)];
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<DidMoveGroupRowResult> {
|
||||
let mut result = DidMoveGroupRowResult {
|
||||
deleted_group: None,
|
||||
row_changesets: vec![],
|
||||
};
|
||||
let cell_rev = match context.row_rev.cells.get(&self.field_id) {
|
||||
Some(cell_rev) => Some(cell_rev.clone()),
|
||||
None => self.default_cell_rev(),
|
||||
None => self.placeholder_cell(),
|
||||
};
|
||||
|
||||
if let Some(cell_rev) = cell_rev {
|
||||
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, context.field_rev, None).1;
|
||||
let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data, context.field_rev, None).1;
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
Ok(self.move_row(&cell_data, context))
|
||||
result.deleted_group = self.delete_group_when_move_row(context.row_rev, &cell_data);
|
||||
result.row_changesets = self.move_row(&cell_data, context);
|
||||
} else {
|
||||
tracing::warn!("Unexpected moving group row, changes should not be empty");
|
||||
Ok(vec![])
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn did_update_group_field(&mut self, _field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>> {
|
||||
@ -298,3 +328,12 @@ struct GroupedRow {
|
||||
row: RowPB,
|
||||
group_id: String,
|
||||
}
|
||||
|
||||
fn get_cell_data_from_row_rev<P: CellProtobufBlobParser>(
|
||||
row_rev: Option<&RowRevision>,
|
||||
field_rev: &FieldRevision,
|
||||
) -> Option<P::Object> {
|
||||
let cell_rev: &CellRevision = row_rev.and_then(|row_rev| row_rev.cells.get(&field_rev.id))?;
|
||||
let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data.clone(), field_rev, None).1;
|
||||
cell_bytes.parser::<P>().ok()
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB};
|
||||
use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
|
||||
use crate::services::group::action::GroupControllerCustomActions;
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
use crate::services::group::controller::{
|
||||
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
|
||||
@ -19,13 +19,13 @@ pub type CheckboxGroupController = GenericGroupController<
|
||||
|
||||
pub type CheckboxGroupContext = GroupContext<CheckboxGroupConfigurationRevision>;
|
||||
|
||||
impl GroupControllerCustomActions for CheckboxGroupController {
|
||||
type CellDataType = CheckboxCellData;
|
||||
fn default_cell_rev(&self) -> Option<CellRevision> {
|
||||
impl GroupCustomize for CheckboxGroupController {
|
||||
type CellData = CheckboxCellData;
|
||||
fn placeholder_cell(&self) -> Option<CellRevision> {
|
||||
Some(CellRevision::new(UNCHECK.to_string()))
|
||||
}
|
||||
|
||||
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool {
|
||||
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
|
||||
if cell_data.is_check() {
|
||||
content == CHECK
|
||||
} else {
|
||||
@ -33,10 +33,10 @@ impl GroupControllerCustomActions for CheckboxGroupController {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_or_remove_row_in_groups_if_match(
|
||||
fn add_or_remove_row_when_cell_changed(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellDataType,
|
||||
cell_data: &Self::CellData,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
@ -79,7 +79,7 @@ impl GroupControllerCustomActions for CheckboxGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_groups(|group| {
|
||||
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||
@ -97,7 +97,7 @@ impl GroupControllerCustomActions for CheckboxGroupController {
|
||||
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &Self::CellDataType,
|
||||
_cell_data: &Self::CellData,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, RowPB};
|
||||
use crate::services::group::action::GroupControllerSharedActions;
|
||||
use crate::entities::{GroupViewChangesetPB, RowPB};
|
||||
use crate::services::group::action::{DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions};
|
||||
use crate::services::group::{Group, GroupController, MoveGroupRowContext};
|
||||
use flowy_error::FlowyResult;
|
||||
use grid_model::{FieldRevision, RowRevision};
|
||||
@ -31,7 +31,7 @@ impl DefaultGroupController {
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupControllerSharedActions for DefaultGroupController {
|
||||
impl GroupControllerActions for DefaultGroupController {
|
||||
fn field_id(&self) -> &str {
|
||||
&self.field_id
|
||||
}
|
||||
@ -57,22 +57,33 @@ impl GroupControllerSharedActions for DefaultGroupController {
|
||||
|
||||
fn did_update_group_row(
|
||||
&mut self,
|
||||
_old_row_rev: &Option<Arc<RowRevision>>,
|
||||
_row_rev: &RowRevision,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
Ok(vec![])
|
||||
) -> FlowyResult<DidUpdateGroupRowResult> {
|
||||
Ok(DidUpdateGroupRowResult {
|
||||
inserted_group: None,
|
||||
deleted_group: None,
|
||||
row_changesets: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn did_delete_delete_row(
|
||||
&mut self,
|
||||
_row_rev: &RowRevision,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
Ok(vec![])
|
||||
) -> FlowyResult<DidMoveGroupRowResult> {
|
||||
Ok(DidMoveGroupRowResult {
|
||||
deleted_group: None,
|
||||
row_changesets: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
todo!()
|
||||
fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<DidMoveGroupRowResult> {
|
||||
Ok(DidMoveGroupRowResult {
|
||||
deleted_group: None,
|
||||
row_changesets: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn did_update_group_field(&mut self, _field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>> {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::entities::{GroupRowsNotificationPB, RowPB};
|
||||
use crate::services::cell::insert_select_option_cell;
|
||||
use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser};
|
||||
use crate::services::group::action::GroupControllerCustomActions;
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
|
||||
use crate::services::group::controller::{
|
||||
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
|
||||
@ -19,17 +19,17 @@ pub type MultiSelectGroupController = GenericGroupController<
|
||||
SelectOptionCellDataParser,
|
||||
>;
|
||||
|
||||
impl GroupControllerCustomActions for MultiSelectGroupController {
|
||||
type CellDataType = SelectOptionCellDataPB;
|
||||
impl GroupCustomize for MultiSelectGroupController {
|
||||
type CellData = SelectOptionCellDataPB;
|
||||
|
||||
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
|
||||
cell_data.select_options.iter().any(|option| option.id == content)
|
||||
}
|
||||
|
||||
fn add_or_remove_row_in_groups_if_match(
|
||||
fn add_or_remove_row_when_cell_changed(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellDataType,
|
||||
cell_data: &Self::CellData,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
@ -40,7 +40,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
|
||||
@ -52,7 +52,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
|
||||
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &Self::CellDataType,
|
||||
_cell_data: &Self::CellData,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::entities::{GroupRowsNotificationPB, RowPB};
|
||||
use crate::services::cell::insert_select_option_cell;
|
||||
use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB};
|
||||
use crate::services::group::action::GroupControllerCustomActions;
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
|
||||
use crate::services::group::controller::{
|
||||
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
|
||||
@ -20,16 +20,16 @@ pub type SingleSelectGroupController = GenericGroupController<
|
||||
SelectOptionCellDataParser,
|
||||
>;
|
||||
|
||||
impl GroupControllerCustomActions for SingleSelectGroupController {
|
||||
type CellDataType = SelectOptionCellDataPB;
|
||||
impl GroupCustomize for SingleSelectGroupController {
|
||||
type CellData = SelectOptionCellDataPB;
|
||||
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
|
||||
cell_data.select_options.iter().any(|option| option.id == content)
|
||||
}
|
||||
|
||||
fn add_or_remove_row_in_groups_if_match(
|
||||
fn add_or_remove_row_when_cell_changed(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellDataType,
|
||||
cell_data: &Self::CellData,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
@ -40,7 +40,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
|
||||
@ -52,7 +52,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
|
||||
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &Self::CellDataType,
|
||||
_cell_data: &Self::CellData,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
|
@ -1,12 +1,13 @@
|
||||
use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB};
|
||||
use crate::entities::{GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowPB};
|
||||
use crate::services::cell::insert_url_cell;
|
||||
use crate::services::field::{URLCellDataPB, URLCellDataParser, URLTypeOptionPB};
|
||||
use crate::services::group::action::GroupControllerCustomActions;
|
||||
use crate::services::field::{URLCellData, URLCellDataPB, URLCellDataParser, URLTypeOptionPB};
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
use crate::services::group::controller::{
|
||||
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
|
||||
};
|
||||
use crate::services::group::{make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroupContext};
|
||||
use flowy_error::FlowyResult;
|
||||
use grid_model::{CellRevision, FieldRevision, GroupRevision, RowRevision, URLGroupConfigurationRevision};
|
||||
|
||||
pub type URLGroupController =
|
||||
@ -14,21 +15,61 @@ pub type URLGroupController =
|
||||
|
||||
pub type URLGroupContext = GroupContext<URLGroupConfigurationRevision>;
|
||||
|
||||
impl GroupControllerCustomActions for URLGroupController {
|
||||
type CellDataType = URLCellDataPB;
|
||||
impl GroupCustomize for URLGroupController {
|
||||
type CellData = URLCellDataPB;
|
||||
|
||||
fn default_cell_rev(&self) -> Option<CellRevision> {
|
||||
fn placeholder_cell(&self) -> Option<CellRevision> {
|
||||
Some(CellRevision::new("".to_string()))
|
||||
}
|
||||
|
||||
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool {
|
||||
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
|
||||
cell_data.content == content
|
||||
}
|
||||
|
||||
fn add_or_remove_row_in_groups_if_match(
|
||||
fn create_or_delete_group_when_cell_changed(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellDataType,
|
||||
old_cell_data: Option<&Self::CellData>,
|
||||
cell_data: &Self::CellData,
|
||||
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
|
||||
// Just return if the group with this url already exists
|
||||
let mut inserted_group = None;
|
||||
if self.group_ctx.get_group(&cell_data.url).is_none() {
|
||||
let cell_data: URLCellData = cell_data.clone().into();
|
||||
let group_revision = make_group_from_url_cell(&cell_data);
|
||||
let mut new_group = self.group_ctx.add_new_group(group_revision)?;
|
||||
new_group.group.rows.push(RowPB::from(row_rev));
|
||||
inserted_group = Some(new_group);
|
||||
}
|
||||
|
||||
// Delete the old url group if there are no rows in that group
|
||||
let deleted_group =
|
||||
match old_cell_data.and_then(|old_cell_data| self.group_ctx.get_group(&old_cell_data.content)) {
|
||||
None => None,
|
||||
Some((_, group)) => {
|
||||
if group.rows.len() == 1 {
|
||||
Some(group.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let deleted_group = match deleted_group {
|
||||
None => None,
|
||||
Some(group) => {
|
||||
self.group_ctx.delete_group(&group.id)?;
|
||||
Some(GroupPB::from(group.clone()))
|
||||
}
|
||||
};
|
||||
|
||||
Ok((inserted_group, deleted_group))
|
||||
}
|
||||
|
||||
fn add_or_remove_row_when_cell_changed(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellData,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
@ -51,7 +92,7 @@ impl GroupControllerCustomActions for URLGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_groups(|group| {
|
||||
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||
@ -69,7 +110,7 @@ impl GroupControllerCustomActions for URLGroupController {
|
||||
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &Self::CellDataType,
|
||||
_cell_data: &Self::CellData,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
@ -80,6 +121,19 @@ impl GroupControllerCustomActions for URLGroupController {
|
||||
});
|
||||
group_changeset
|
||||
}
|
||||
|
||||
fn delete_group_when_move_row(&mut self, _row_rev: &RowRevision, cell_data: &Self::CellData) -> Option<GroupPB> {
|
||||
let mut deleted_group = None;
|
||||
if let Some((_, group)) = self.group_ctx.get_group(&cell_data.content) {
|
||||
if group.rows.len() == 1 {
|
||||
deleted_group = Some(GroupPB::from(group.clone()));
|
||||
}
|
||||
}
|
||||
if deleted_group.is_some() {
|
||||
let _ = self.group_ctx.delete_group(&deleted_group.as_ref().unwrap().group_id);
|
||||
}
|
||||
deleted_group
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for URLGroupController {
|
||||
@ -117,13 +171,9 @@ impl GroupGenerator for URLGroupGenerator {
|
||||
let group_configs = cells
|
||||
.into_iter()
|
||||
.flat_map(|value| value.into_url_field_cell_data())
|
||||
.map(|cell| {
|
||||
let group_id = cell.content.clone();
|
||||
let group_name = cell.content.clone();
|
||||
GeneratedGroupConfig {
|
||||
group_rev: GroupRevision::new(group_id, group_name),
|
||||
filter_content: cell.content,
|
||||
}
|
||||
.map(|cell| GeneratedGroupConfig {
|
||||
group_rev: make_group_from_url_cell(&cell),
|
||||
filter_content: cell.content,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -134,3 +184,9 @@ impl GroupGenerator for URLGroupGenerator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_group_from_url_cell(cell: &URLCellData) -> GroupRevision {
|
||||
let group_id = cell.content.clone();
|
||||
let group_name = cell.content.clone();
|
||||
GroupRevision::new(group_id, group_name)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::entities::RowPB;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Debug, Eq)]
|
||||
pub struct Group {
|
||||
pub id: String,
|
||||
pub field_id: String,
|
||||
|
@ -240,29 +240,44 @@ impl DatabaseViewRevisionEditor {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn did_delete_view_row(&self, row_rev: &RowRevision) {
|
||||
// Send the group notification if the current view has groups;
|
||||
let changesets = self
|
||||
let result = self
|
||||
.mut_group_controller(|group_controller, field_rev| {
|
||||
group_controller.did_delete_delete_row(row_rev, &field_rev)
|
||||
})
|
||||
.await;
|
||||
|
||||
tracing::trace!("Delete row in view changeset: {:?}", changesets);
|
||||
if let Some(changesets) = changesets {
|
||||
for changeset in changesets {
|
||||
if let Some(result) = result {
|
||||
tracing::trace!("Delete row in view changeset: {:?}", result.row_changesets);
|
||||
for changeset in result.row_changesets {
|
||||
self.notify_did_update_group_rows(changeset).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn did_update_view_cell(&self, row_rev: &RowRevision) {
|
||||
let changesets = self
|
||||
pub async fn did_update_view_row(&self, old_row_rev: Option<Arc<RowRevision>>, row_rev: &RowRevision) {
|
||||
let result = self
|
||||
.mut_group_controller(|group_controller, field_rev| {
|
||||
group_controller.did_update_group_row(row_rev, &field_rev)
|
||||
Ok(group_controller.did_update_group_row(&old_row_rev, row_rev, &field_rev))
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(changesets) = changesets {
|
||||
for changeset in changesets {
|
||||
if let Some(Ok(result)) = result {
|
||||
let mut changeset = GroupViewChangesetPB {
|
||||
view_id: self.view_id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(inserted_group) = result.inserted_group {
|
||||
tracing::trace!("Create group after editing the row: {:?}", inserted_group);
|
||||
changeset.inserted_groups.push(inserted_group);
|
||||
}
|
||||
if let Some(delete_group) = result.deleted_group {
|
||||
tracing::trace!("Delete group after editing the row: {:?}", delete_group);
|
||||
changeset.deleted_groups.push(delete_group.group_id);
|
||||
}
|
||||
self.notify_did_update_view(changeset).await;
|
||||
|
||||
tracing::trace!("Group changesets after editing the row: {:?}", result.row_changesets);
|
||||
for changeset in result.row_changesets {
|
||||
self.notify_did_update_group_rows(changeset).await;
|
||||
}
|
||||
}
|
||||
@ -282,8 +297,8 @@ impl DatabaseViewRevisionEditor {
|
||||
row_changeset: &mut RowChangeset,
|
||||
to_group_id: &str,
|
||||
to_row_id: Option<String>,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let changesets = self
|
||||
) {
|
||||
let result = self
|
||||
.mut_group_controller(|group_controller, field_rev| {
|
||||
let move_row_context = MoveGroupRowContext {
|
||||
row_rev,
|
||||
@ -292,13 +307,25 @@ impl DatabaseViewRevisionEditor {
|
||||
to_group_id,
|
||||
to_row_id,
|
||||
};
|
||||
|
||||
let changesets = group_controller.move_group_row(move_row_context)?;
|
||||
Ok(changesets)
|
||||
group_controller.move_group_row(move_row_context)
|
||||
})
|
||||
.await;
|
||||
|
||||
changesets.unwrap_or_default()
|
||||
if let Some(result) = result {
|
||||
let mut changeset = GroupViewChangesetPB {
|
||||
view_id: self.view_id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(delete_group) = result.deleted_group {
|
||||
tracing::info!("Delete group after moving the row: {:?}", delete_group);
|
||||
changeset.deleted_groups.push(delete_group.group_id);
|
||||
}
|
||||
self.notify_did_update_view(changeset).await;
|
||||
|
||||
for changeset in result.row_changesets {
|
||||
self.notify_did_update_group_rows(changeset).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Only call once after grid view editor initialized
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
@ -334,7 +361,7 @@ impl DatabaseViewRevisionEditor {
|
||||
inserted_groups: vec![inserted_group],
|
||||
deleted_groups: vec![params.from_group_id.clone()],
|
||||
update_groups: vec![],
|
||||
new_groups: vec![],
|
||||
initial_groups: vec![],
|
||||
};
|
||||
|
||||
self.notify_did_update_view(changeset).await;
|
||||
@ -610,7 +637,7 @@ impl DatabaseViewRevisionEditor {
|
||||
*self.group_controller.write().await = new_group_controller;
|
||||
let changeset = GroupViewChangesetPB {
|
||||
view_id: self.view_id.clone(),
|
||||
new_groups,
|
||||
initial_groups: new_groups,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -88,14 +88,14 @@ impl DatabaseViewManager {
|
||||
}
|
||||
|
||||
/// Insert/Delete the group's row if the corresponding cell data was changed.
|
||||
pub async fn did_update_cell(&self, row_id: &str) {
|
||||
pub async fn did_update_row(&self, old_row_rev: Option<Arc<RowRevision>>, row_id: &str) {
|
||||
match self.delegate.get_row_rev(row_id).await {
|
||||
None => {
|
||||
tracing::warn!("Can not find the row in grid view");
|
||||
}
|
||||
Some((_, row_rev)) => {
|
||||
for view_editor in self.view_editors.read().await.values() {
|
||||
view_editor.did_update_view_cell(&row_rev).await;
|
||||
view_editor.did_update_view_row(old_row_rev.clone(), &row_rev).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,7 +192,7 @@ impl DatabaseViewManager {
|
||||
) -> FlowyResult<()> {
|
||||
let mut row_changeset = RowChangeset::new(row_rev.id.clone());
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
let group_changesets = view_editor
|
||||
view_editor
|
||||
.move_view_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone())
|
||||
.await;
|
||||
|
||||
@ -200,10 +200,6 @@ impl DatabaseViewManager {
|
||||
recv_row_changeset(row_changeset).await;
|
||||
}
|
||||
|
||||
for group_changeset in group_changesets {
|
||||
view_editor.notify_did_update_group_rows(group_changeset).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::grid::block_test::script::GridRowTest;
|
||||
use crate::grid::block_test::script::DatabaseRowTest;
|
||||
use crate::grid::block_test::script::RowScript::*;
|
||||
|
||||
use grid_model::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset};
|
||||
@ -11,7 +11,7 @@ async fn grid_create_block() {
|
||||
CreateBlock { block: block_meta_rev },
|
||||
AssertBlockCount(2),
|
||||
];
|
||||
GridRowTest::new().await.run_scripts(scripts).await;
|
||||
DatabaseRowTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -37,5 +37,5 @@ async fn grid_update_block() {
|
||||
block: cloned_grid_block,
|
||||
},
|
||||
];
|
||||
GridRowTest::new().await.run_scripts(scripts).await;
|
||||
DatabaseRowTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
use crate::grid::block_test::script::RowScript::*;
|
||||
use crate::grid::block_test::script::{CreateRowScriptBuilder, GridRowTest};
|
||||
use crate::grid::grid_editor::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, TWITTER};
|
||||
use crate::grid::block_test::script::{CreateRowScriptBuilder, DatabaseRowTest};
|
||||
use crate::grid::mock_data::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, TWITTER};
|
||||
use flowy_database::entities::FieldType;
|
||||
use flowy_database::services::field::{SELECTION_IDS_SEPARATOR, UNCHECK};
|
||||
use grid_model::RowChangeset;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_row_count_test() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut test = DatabaseRowTest::new().await;
|
||||
let scripts = vec![
|
||||
AssertRowCount(6),
|
||||
CreateEmptyRow,
|
||||
@ -22,7 +22,7 @@ async fn grid_create_row_count_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_row() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut test = DatabaseRowTest::new().await;
|
||||
let row_rev = test.row_builder().build();
|
||||
let changeset = RowChangeset {
|
||||
row_id: row_rev.id.clone(),
|
||||
@ -41,7 +41,7 @@ async fn grid_update_row() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_delete_row() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut test = DatabaseRowTest::new().await;
|
||||
let row_1 = test.row_builder().build();
|
||||
let row_2 = test.row_builder().build();
|
||||
let row_ids = vec![row_1.id.clone(), row_2.id.clone()];
|
||||
@ -67,7 +67,7 @@ async fn grid_delete_row() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_row_add_cells_test() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut test = DatabaseRowTest::new().await;
|
||||
let mut builder = CreateRowScriptBuilder::new(&test);
|
||||
builder.insert(FieldType::RichText, "hello world", "hello world");
|
||||
builder.insert(FieldType::DateTime, "1647251762", "2022/03/14");
|
||||
@ -85,7 +85,7 @@ async fn grid_row_add_cells_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_row_insert_number_test() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut test = DatabaseRowTest::new().await;
|
||||
for (val, expected) in &[("1647251762", "2022/03/14"), ("2022/03/14", ""), ("", "")] {
|
||||
let mut builder = CreateRowScriptBuilder::new(&test);
|
||||
builder.insert(FieldType::DateTime, val, expected);
|
||||
@ -96,7 +96,7 @@ async fn grid_row_insert_number_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_row_insert_date_test() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut test = DatabaseRowTest::new().await;
|
||||
for (val, expected) in &[
|
||||
("18,443", "$18,443.00"),
|
||||
("0", "$0.00"),
|
||||
@ -112,7 +112,7 @@ async fn grid_row_insert_date_test() {
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn grid_row_insert_single_select_test() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut test = DatabaseRowTest::new().await;
|
||||
let mut builder = CreateRowScriptBuilder::new(&test);
|
||||
builder.insert_single_select_cell(|mut options| options.pop().unwrap(), PAUSED);
|
||||
let scripts = builder.build();
|
||||
@ -121,7 +121,7 @@ async fn grid_row_insert_single_select_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_row_insert_multi_select_test() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut test = DatabaseRowTest::new().await;
|
||||
let mut builder = CreateRowScriptBuilder::new(&test);
|
||||
builder.insert_multi_select_cell(
|
||||
|mut options| {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::grid::block_test::script::RowScript::{AssertCell, CreateRow};
|
||||
use crate::grid::block_test::util::GridRowTestBuilder;
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
use crate::grid::database_editor::DatabaseEditorTest;
|
||||
use flowy_database::entities::{CellPathParams, CreateRowParams, DatabaseViewLayout, FieldType, RowPB};
|
||||
use flowy_database::services::field::*;
|
||||
use flowy_database::services::row::DatabaseBlockRow;
|
||||
@ -48,13 +48,13 @@ pub enum RowScript {
|
||||
},
|
||||
}
|
||||
|
||||
pub struct GridRowTest {
|
||||
inner: GridEditorTest,
|
||||
pub struct DatabaseRowTest {
|
||||
inner: DatabaseEditorTest,
|
||||
}
|
||||
|
||||
impl GridRowTest {
|
||||
impl DatabaseRowTest {
|
||||
pub async fn new() -> Self {
|
||||
let editor_test = GridEditorTest::new_table().await;
|
||||
let editor_test = DatabaseEditorTest::new_table().await;
|
||||
Self { inner: editor_test }
|
||||
}
|
||||
|
||||
@ -282,15 +282,15 @@ fn block_from_row_pbs(row_orders: Vec<RowPB>) -> Vec<DatabaseBlockRow> {
|
||||
map.into_values().collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
impl std::ops::Deref for GridRowTest {
|
||||
type Target = GridEditorTest;
|
||||
impl std::ops::Deref for DatabaseRowTest {
|
||||
type Target = DatabaseEditorTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for GridRowTest {
|
||||
impl std::ops::DerefMut for DatabaseRowTest {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
@ -303,7 +303,7 @@ pub struct CreateRowScriptBuilder<'a> {
|
||||
}
|
||||
|
||||
impl<'a> CreateRowScriptBuilder<'a> {
|
||||
pub fn new(test: &'a GridRowTest) -> Self {
|
||||
pub fn new(test: &'a DatabaseRowTest) -> Self {
|
||||
Self {
|
||||
builder: test.row_builder(),
|
||||
data_by_field_type: HashMap::new(),
|
||||
|
@ -1,17 +1,17 @@
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
use crate::grid::database_editor::DatabaseEditorTest;
|
||||
use flowy_database::entities::CellChangesetPB;
|
||||
|
||||
pub enum CellScript {
|
||||
UpdateCell { changeset: CellChangesetPB, is_err: bool },
|
||||
}
|
||||
|
||||
pub struct GridCellTest {
|
||||
inner: GridEditorTest,
|
||||
pub struct DatabaseCellTest {
|
||||
inner: DatabaseEditorTest,
|
||||
}
|
||||
|
||||
impl GridCellTest {
|
||||
impl DatabaseCellTest {
|
||||
pub async fn new() -> Self {
|
||||
let inner = GridEditorTest::new_table().await;
|
||||
let inner = DatabaseEditorTest::new_table().await;
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
@ -48,15 +48,15 @@ impl GridCellTest {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for GridCellTest {
|
||||
type Target = GridEditorTest;
|
||||
impl std::ops::Deref for DatabaseCellTest {
|
||||
type Target = DatabaseEditorTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for GridCellTest {
|
||||
impl std::ops::DerefMut for DatabaseCellTest {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::grid::cell_test::script::CellScript::*;
|
||||
use crate::grid::cell_test::script::GridCellTest;
|
||||
use crate::grid::cell_test::script::DatabaseCellTest;
|
||||
use crate::grid::field_test::util::make_date_cell_string;
|
||||
use flowy_database::entities::{CellChangesetPB, FieldType};
|
||||
use flowy_database::services::cell::ToCellChangesetString;
|
||||
@ -8,7 +8,7 @@ use flowy_database::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOpti
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_cell_update() {
|
||||
let mut test = GridCellTest::new().await;
|
||||
let mut test = DatabaseCellTest::new().await;
|
||||
let field_revs = &test.field_revs;
|
||||
let row_revs = &test.row_revs;
|
||||
let grid_blocks = &test.block_meta_revs;
|
||||
@ -60,7 +60,7 @@ async fn grid_cell_update() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn text_cell_date_test() {
|
||||
let test = GridCellTest::new().await;
|
||||
let test = DatabaseCellTest::new().await;
|
||||
let text_field = test.get_first_field_rev(FieldType::RichText);
|
||||
let cells = test
|
||||
.editor
|
||||
@ -84,7 +84,7 @@ async fn text_cell_date_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn url_cell_date_test() {
|
||||
let test = GridCellTest::new().await;
|
||||
let test = DatabaseCellTest::new().await;
|
||||
let url_field = test.get_first_field_rev(FieldType::URL);
|
||||
let cells = test
|
||||
.editor
|
||||
|
193
frontend/rust-lib/flowy-database/tests/grid/database_editor.rs
Normal file
193
frontend/rust-lib/flowy-database/tests/grid/database_editor.rs
Normal file
@ -0,0 +1,193 @@
|
||||
use crate::grid::mock_data::*;
|
||||
use bytes::Bytes;
|
||||
use flowy_database::entities::*;
|
||||
use flowy_database::services::cell::ToCellChangesetString;
|
||||
use flowy_database::services::field::SelectOptionPB;
|
||||
use flowy_database::services::field::*;
|
||||
use flowy_database::services::grid_editor::DatabaseRevisionEditor;
|
||||
use flowy_test::helper::ViewTest;
|
||||
use flowy_test::FlowySDKTest;
|
||||
use grid_model::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use strum::EnumCount;
|
||||
|
||||
pub struct DatabaseEditorTest {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub view_id: String,
|
||||
pub editor: Arc<DatabaseRevisionEditor>,
|
||||
pub field_revs: Vec<Arc<FieldRevision>>,
|
||||
pub block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
|
||||
pub row_revs: Vec<Arc<RowRevision>>,
|
||||
pub field_count: usize,
|
||||
pub row_by_row_id: HashMap<String, RowPB>,
|
||||
}
|
||||
|
||||
impl DatabaseEditorTest {
|
||||
pub async fn new_table() -> Self {
|
||||
Self::new(DatabaseViewLayout::Grid).await
|
||||
}
|
||||
|
||||
pub async fn new_board() -> Self {
|
||||
Self::new(DatabaseViewLayout::Board).await
|
||||
}
|
||||
|
||||
pub async fn new(layout: DatabaseViewLayout) -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let _ = sdk.init_user().await;
|
||||
let test = match layout {
|
||||
DatabaseViewLayout::Grid => {
|
||||
let build_context = make_test_grid();
|
||||
let view_data: Bytes = build_context.into();
|
||||
ViewTest::new_grid_view(&sdk, view_data.to_vec()).await
|
||||
}
|
||||
DatabaseViewLayout::Board => {
|
||||
let build_context = make_test_board();
|
||||
let view_data: Bytes = build_context.into();
|
||||
ViewTest::new_board_view(&sdk, view_data.to_vec()).await
|
||||
}
|
||||
DatabaseViewLayout::Calendar => {
|
||||
let build_context = make_test_calendar();
|
||||
let view_data: Bytes = build_context.into();
|
||||
ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await
|
||||
}
|
||||
};
|
||||
|
||||
let editor = sdk.grid_manager.open_database(&test.view.id).await.unwrap();
|
||||
let field_revs = editor.get_field_revs(None).await.unwrap();
|
||||
let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
|
||||
let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap();
|
||||
assert_eq!(block_meta_revs.len(), 1);
|
||||
|
||||
// It seems like you should add the field in the make_test_grid() function.
|
||||
// Because we assert the initialize count of the fields is equal to FieldType::COUNT.
|
||||
assert_eq!(field_revs.len(), FieldType::COUNT);
|
||||
|
||||
let grid_id = test.view.id;
|
||||
Self {
|
||||
sdk,
|
||||
view_id: grid_id,
|
||||
editor,
|
||||
field_revs,
|
||||
block_meta_revs,
|
||||
row_revs: row_pbs,
|
||||
field_count: FieldType::COUNT,
|
||||
row_by_row_id: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
|
||||
self.editor.get_all_row_revs(&self.view_id).await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn grid_filters(&self) -> Vec<FilterPB> {
|
||||
self.editor.get_all_filters().await.unwrap()
|
||||
}
|
||||
|
||||
pub fn get_field_rev(&self, field_id: &str, field_type: FieldType) -> &Arc<FieldRevision> {
|
||||
self.field_revs
|
||||
.iter()
|
||||
.filter(|field_rev| {
|
||||
let t_field_type: FieldType = field_rev.ty.into();
|
||||
field_rev.id == field_id && t_field_type == field_type
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.pop()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// returns the first `FieldRevision` in the build-in test grid.
|
||||
/// Not support duplicate `FieldType` in test grid yet.
|
||||
pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc<FieldRevision> {
|
||||
self.field_revs
|
||||
.iter()
|
||||
.filter(|field_rev| {
|
||||
let t_field_type: FieldType = field_rev.ty.into();
|
||||
t_field_type == field_type
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.pop()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec<SelectOptionPB> {
|
||||
let field_type = FieldType::MultiSelect;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
let type_option = field_rev
|
||||
.get_type_option::<MultiSelectTypeOptionPB>(field_type.into())
|
||||
.unwrap();
|
||||
type_option.options
|
||||
}
|
||||
|
||||
pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOptionPB {
|
||||
let field_type = FieldType::SingleSelect;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
field_rev
|
||||
.get_type_option::<SingleSelectTypeOptionPB>(field_type.into())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB {
|
||||
let field_type = FieldType::Checklist;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
field_rev
|
||||
.get_type_option::<ChecklistTypeOptionPB>(field_type.into())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB {
|
||||
let field_type = FieldType::Checkbox;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
field_rev
|
||||
.get_type_option::<CheckboxTypeOptionPB>(field_type.into())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn block_id(&self) -> &str {
|
||||
&self.block_meta_revs.last().unwrap().block_id
|
||||
}
|
||||
|
||||
pub async fn update_cell<T: ToCellChangesetString>(&mut self, field_id: &str, row_id: String, cell_changeset: T) {
|
||||
let field_rev = self
|
||||
.field_revs
|
||||
.iter()
|
||||
.find(|field_rev| field_rev.id == field_id)
|
||||
.unwrap();
|
||||
|
||||
self.editor
|
||||
.update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) {
|
||||
let field_rev = self
|
||||
.field_revs
|
||||
.iter()
|
||||
.find(|field_rev| {
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
field_type == FieldType::RichText
|
||||
})
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
self.update_cell(&field_rev.id, row_id, content.to_string()).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) {
|
||||
let field_rev = self
|
||||
.field_revs
|
||||
.iter()
|
||||
.find(|field_rev| {
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
field_type == FieldType::SingleSelect
|
||||
})
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(option_id);
|
||||
self.update_cell(&field_rev.id, row_id, cell_changeset).await;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
use crate::grid::database_editor::DatabaseEditorTest;
|
||||
use flowy_database::entities::{CreateFieldParams, FieldChangesetParams, FieldType};
|
||||
use flowy_database::services::cell::{stringify_cell_data, TypeCellData};
|
||||
use grid_model::FieldRevision;
|
||||
@ -38,13 +38,13 @@ pub enum FieldScript {
|
||||
},
|
||||
}
|
||||
|
||||
pub struct GridFieldTest {
|
||||
inner: GridEditorTest,
|
||||
pub struct DatabaseFieldTest {
|
||||
inner: DatabaseEditorTest,
|
||||
}
|
||||
|
||||
impl GridFieldTest {
|
||||
impl DatabaseFieldTest {
|
||||
pub async fn new() -> Self {
|
||||
let editor_test = GridEditorTest::new_table().await;
|
||||
let editor_test = DatabaseEditorTest::new_table().await;
|
||||
Self { inner: editor_test }
|
||||
}
|
||||
|
||||
@ -144,15 +144,15 @@ impl GridFieldTest {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for GridFieldTest {
|
||||
type Target = GridEditorTest;
|
||||
impl std::ops::Deref for DatabaseFieldTest {
|
||||
type Target = DatabaseEditorTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for GridFieldTest {
|
||||
impl std::ops::DerefMut for DatabaseFieldTest {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::grid::field_test::script::DatabaseFieldTest;
|
||||
use crate::grid::field_test::script::FieldScript::*;
|
||||
use crate::grid::field_test::script::GridFieldTest;
|
||||
use crate::grid::field_test::util::*;
|
||||
use bytes::Bytes;
|
||||
use flowy_database::entities::{FieldChangesetParams, FieldType};
|
||||
@ -8,7 +8,7 @@ use flowy_database::services::field::{gen_option_id, SingleSelectTypeOptionPB, C
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_field() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let (params, field_rev) = create_text_field(&test.view_id());
|
||||
|
||||
let scripts = vec![
|
||||
@ -33,7 +33,7 @@ async fn grid_create_field() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_duplicate_field() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let (params, _) = create_text_field(&test.view_id());
|
||||
let field_count = test.field_count();
|
||||
let expected_field_count = field_count + 1;
|
||||
@ -46,7 +46,7 @@ async fn grid_create_duplicate_field() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_field_with_empty_change() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let (params, _) = create_single_select_field(&test.view_id());
|
||||
let create_field_index = test.field_count();
|
||||
let scripts = vec![CreateField { params }];
|
||||
@ -71,7 +71,7 @@ async fn grid_update_field_with_empty_change() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_field() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let (params, _) = create_single_select_field(&test.view_id());
|
||||
let scripts = vec![CreateField { params }];
|
||||
let create_field_index = test.field_count();
|
||||
@ -107,7 +107,7 @@ async fn grid_update_field() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_delete_field() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let original_field_count = test.field_count();
|
||||
let (params, _) = create_text_field(&test.view_id());
|
||||
let scripts = vec![CreateField { params }];
|
||||
@ -125,7 +125,7 @@ async fn grid_delete_field() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_switch_from_select_option_to_checkbox_test() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::SingleSelect);
|
||||
|
||||
// Update the type option data of single select option
|
||||
@ -160,7 +160,7 @@ async fn grid_switch_from_select_option_to_checkbox_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_switch_from_checkbox_to_select_option_test() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::Checkbox).clone();
|
||||
let scripts = vec![
|
||||
// switch to single-select field type
|
||||
@ -203,7 +203,7 @@ async fn grid_switch_from_checkbox_to_select_option_test() {
|
||||
// option1, option2 -> "option1.name, option2.name"
|
||||
#[tokio::test]
|
||||
async fn grid_switch_from_multi_select_to_text_test() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::MultiSelect).clone();
|
||||
|
||||
let multi_select_type_option = test.get_multi_select_type_option(&field_rev.id);
|
||||
@ -235,7 +235,7 @@ async fn grid_switch_from_multi_select_to_text_test() {
|
||||
// unchecked -> ""
|
||||
#[tokio::test]
|
||||
async fn grid_switch_from_checkbox_to_text_test() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::Checkbox);
|
||||
|
||||
let scripts = vec![
|
||||
@ -265,7 +265,7 @@ async fn grid_switch_from_checkbox_to_text_test() {
|
||||
// "" -> unchecked
|
||||
#[tokio::test]
|
||||
async fn grid_switch_from_text_to_checkbox_test() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::RichText).clone();
|
||||
|
||||
let scripts = vec![
|
||||
@ -288,7 +288,7 @@ async fn grid_switch_from_text_to_checkbox_test() {
|
||||
// 1647251762 -> Mar 14,2022 (This string will be different base on current data setting)
|
||||
#[tokio::test]
|
||||
async fn grid_switch_from_date_to_text_test() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::DateTime).clone();
|
||||
let scripts = vec![
|
||||
SwitchToField {
|
||||
@ -316,7 +316,7 @@ async fn grid_switch_from_date_to_text_test() {
|
||||
// $1 -> "$1"(This string will be different base on current data setting)
|
||||
#[tokio::test]
|
||||
async fn grid_switch_from_number_to_text_test() {
|
||||
let mut test = GridFieldTest::new().await;
|
||||
let mut test = DatabaseFieldTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::Number).clone();
|
||||
|
||||
let scripts = vec![
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
|
||||
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
|
||||
use flowy_database::entities::CheckboxFilterConditionPB;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checkbox_is_check_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
// The initial number of unchecked is 3
|
||||
// The initial number of checked is 2
|
||||
@ -20,7 +20,7 @@ async fn grid_filter_checkbox_is_check_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checkbox_is_uncheck_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let expected = 3;
|
||||
let row_count = test.row_revs.len();
|
||||
let scripts = vec![
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
|
||||
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
|
||||
use flowy_database::entities::ChecklistFilterConditionPB;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checklist_is_incomplete_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let expected = 5;
|
||||
let row_count = test.row_revs.len();
|
||||
let scripts = vec![
|
||||
@ -22,7 +22,7 @@ async fn grid_filter_checklist_is_incomplete_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checklist_is_complete_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let expected = 1;
|
||||
let row_count = test.row_revs.len();
|
||||
let scripts = vec![
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
|
||||
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
|
||||
use flowy_database::entities::DateFilterConditionPB;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_date_is_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 3;
|
||||
let scripts = vec![
|
||||
@ -25,7 +25,7 @@ async fn grid_filter_date_is_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_date_after_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 3;
|
||||
let scripts = vec![
|
||||
@ -46,7 +46,7 @@ async fn grid_filter_date_after_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_date_on_or_after_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 3;
|
||||
let scripts = vec![
|
||||
@ -67,7 +67,7 @@ async fn grid_filter_date_on_or_after_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_date_on_or_before_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 4;
|
||||
let scripts = vec![
|
||||
@ -88,7 +88,7 @@ async fn grid_filter_date_on_or_before_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_date_within_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 5;
|
||||
let scripts = vec![
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
|
||||
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
|
||||
use flowy_database::entities::NumberFilterConditionPB;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_equal_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 1;
|
||||
let scripts = vec![
|
||||
@ -23,7 +23,7 @@ async fn grid_filter_number_is_equal_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_less_than_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 2;
|
||||
let scripts = vec![
|
||||
@ -43,7 +43,7 @@ async fn grid_filter_number_is_less_than_test() {
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn grid_filter_number_is_less_than_test2() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 2;
|
||||
let scripts = vec![
|
||||
@ -62,7 +62,7 @@ async fn grid_filter_number_is_less_than_test2() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_less_than_or_equal_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 3;
|
||||
let scripts = vec![
|
||||
@ -81,7 +81,7 @@ async fn grid_filter_number_is_less_than_or_equal_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 1;
|
||||
let scripts = vec![
|
||||
@ -100,7 +100,7 @@ async fn grid_filter_number_is_empty_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_not_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_revs.len();
|
||||
let expected = 5;
|
||||
let scripts = vec![
|
||||
|
@ -15,7 +15,7 @@ use flowy_sqlite::schema::view_table::dsl::view_table;
|
||||
use flowy_database::services::cell::insert_select_option_cell;
|
||||
use flowy_database::services::filter::FilterType;
|
||||
use flowy_database::services::view_editor::GridViewChanged;
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
use crate::grid::database_editor::DatabaseEditorTest;
|
||||
|
||||
pub struct FilterRowChanged {
|
||||
pub(crate) showing_num_of_rows: usize,
|
||||
@ -99,14 +99,14 @@ pub enum FilterScript {
|
||||
Wait { millisecond: u64 }
|
||||
}
|
||||
|
||||
pub struct GridFilterTest {
|
||||
inner: GridEditorTest,
|
||||
pub struct DatabaseFilterTest {
|
||||
inner: DatabaseEditorTest,
|
||||
recv: Option<Receiver<GridViewChanged>>,
|
||||
}
|
||||
|
||||
impl GridFilterTest {
|
||||
impl DatabaseFilterTest {
|
||||
pub async fn new() -> Self {
|
||||
let editor_test = GridEditorTest::new_table().await;
|
||||
let editor_test = DatabaseEditorTest::new_table().await;
|
||||
Self {
|
||||
inner: editor_test,
|
||||
recv: None,
|
||||
@ -298,15 +298,15 @@ impl GridFilterTest {
|
||||
}
|
||||
|
||||
|
||||
impl std::ops::Deref for GridFilterTest {
|
||||
type Target = GridEditorTest;
|
||||
impl std::ops::Deref for DatabaseFilterTest {
|
||||
type Target = DatabaseEditorTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for GridFilterTest {
|
||||
impl std::ops::DerefMut for DatabaseFilterTest {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
|
||||
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
|
||||
use flowy_database::entities::{FieldType, SelectOptionConditionPB};
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_multi_select_is_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateMultiSelectFilter {
|
||||
condition: SelectOptionConditionPB::OptionIsEmpty,
|
||||
@ -17,7 +17,7 @@ async fn grid_filter_multi_select_is_empty_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_multi_select_is_not_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateMultiSelectFilter {
|
||||
condition: SelectOptionConditionPB::OptionIsNotEmpty,
|
||||
@ -30,7 +30,7 @@ async fn grid_filter_multi_select_is_not_empty_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_multi_select_is_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::MultiSelect);
|
||||
let mut options = test.get_multi_select_type_option(&field_rev.id);
|
||||
let scripts = vec![
|
||||
@ -45,7 +45,7 @@ async fn grid_filter_multi_select_is_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_multi_select_is_test2() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::MultiSelect);
|
||||
let mut options = test.get_multi_select_type_option(&field_rev.id);
|
||||
let scripts = vec![
|
||||
@ -60,7 +60,7 @@ async fn grid_filter_multi_select_is_test2() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_single_select_is_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let expected = 2;
|
||||
let row_count = test.row_revs.len();
|
||||
let scripts = vec![
|
||||
@ -79,7 +79,7 @@ async fn grid_filter_single_select_is_empty_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_single_select_is_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::SingleSelect);
|
||||
let mut options = test.get_single_select_type_option(&field_rev.id).options;
|
||||
let expected = 2;
|
||||
@ -100,7 +100,7 @@ async fn grid_filter_single_select_is_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_single_select_is_test2() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::SingleSelect);
|
||||
let row_revs = test.get_row_revs().await;
|
||||
let mut options = test.get_single_select_type_option(&field_rev.id).options;
|
||||
|
@ -5,7 +5,7 @@ use flowy_database::services::filter::FilterType;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_text_is_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterConditionPB::TextIsEmpty,
|
||||
@ -22,7 +22,7 @@ async fn grid_filter_text_is_empty_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_text_is_not_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
// Only one row's text of the initial rows is ""
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
@ -55,7 +55,7 @@ async fn grid_filter_text_is_not_empty_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_is_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
// Only one row's text of the initial rows is "A"
|
||||
let scripts = vec![CreateTextFilter {
|
||||
condition: TextFilterConditionPB::Is,
|
||||
@ -70,7 +70,7 @@ async fn grid_filter_is_text_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_contain_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let scripts = vec![CreateTextFilter {
|
||||
condition: TextFilterConditionPB::Contains,
|
||||
content: "A".to_string(),
|
||||
@ -84,7 +84,7 @@ async fn grid_filter_contain_text_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_contain_text_test2() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_revs = test.row_revs.clone();
|
||||
|
||||
let scripts = vec![
|
||||
@ -110,7 +110,7 @@ async fn grid_filter_contain_text_test2() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_does_not_contain_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
// None of the initial rows contains the text "AB"
|
||||
let scripts = vec![CreateTextFilter {
|
||||
condition: TextFilterConditionPB::DoesNotContain,
|
||||
@ -125,7 +125,7 @@ async fn grid_filter_does_not_contain_text_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_start_with_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let scripts = vec![CreateTextFilter {
|
||||
condition: TextFilterConditionPB::StartsWith,
|
||||
content: "A".to_string(),
|
||||
@ -139,7 +139,7 @@ async fn grid_filter_start_with_text_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_ends_with_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterConditionPB::EndsWith,
|
||||
@ -153,7 +153,7 @@ async fn grid_filter_ends_with_text_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_text_filter_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterConditionPB::EndsWith,
|
||||
@ -187,7 +187,7 @@ async fn grid_update_text_filter_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_delete_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let field_rev = test.get_first_field_rev(FieldType::RichText).clone();
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterConditionPB::TextIsEmpty,
|
||||
@ -216,7 +216,7 @@ async fn grid_filter_delete_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_update_empty_text_cell_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_revs = test.row_revs.clone();
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
|
@ -1,573 +0,0 @@
|
||||
#![allow(clippy::all)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
use crate::grid::block_test::util::GridRowTestBuilder;
|
||||
use bytes::Bytes;
|
||||
use flowy_client_sync::client_database::DatabaseBuilder;
|
||||
use flowy_database::entities::*;
|
||||
use flowy_database::services::cell::ToCellChangesetString;
|
||||
use flowy_database::services::field::SelectOptionPB;
|
||||
use flowy_database::services::field::*;
|
||||
use flowy_database::services::grid_editor::{DatabaseRevisionEditor, GridRevisionSerde};
|
||||
use flowy_database::services::row::{CreateRowRevisionPayload, RowRevisionBuilder};
|
||||
use flowy_database::services::setting::GridSettingChangesetBuilder;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
|
||||
use flowy_test::helper::ViewTest;
|
||||
use flowy_test::FlowySDKTest;
|
||||
use grid_model::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use strum::EnumCount;
|
||||
use strum::IntoEnumIterator;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub struct GridEditorTest {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub view_id: String,
|
||||
pub editor: Arc<DatabaseRevisionEditor>,
|
||||
pub field_revs: Vec<Arc<FieldRevision>>,
|
||||
pub block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
|
||||
pub row_revs: Vec<Arc<RowRevision>>,
|
||||
pub field_count: usize,
|
||||
pub row_by_row_id: HashMap<String, RowPB>,
|
||||
}
|
||||
|
||||
impl GridEditorTest {
|
||||
pub async fn new_table() -> Self {
|
||||
Self::new(DatabaseViewLayout::Grid).await
|
||||
}
|
||||
|
||||
pub async fn new_board() -> Self {
|
||||
Self::new(DatabaseViewLayout::Board).await
|
||||
}
|
||||
|
||||
pub async fn new(layout: DatabaseViewLayout) -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let _ = sdk.init_user().await;
|
||||
let test = match layout {
|
||||
DatabaseViewLayout::Grid => {
|
||||
let build_context = make_test_grid();
|
||||
let view_data: Bytes = build_context.into();
|
||||
ViewTest::new_grid_view(&sdk, view_data.to_vec()).await
|
||||
}
|
||||
DatabaseViewLayout::Board => {
|
||||
let build_context = make_test_board();
|
||||
let view_data: Bytes = build_context.into();
|
||||
ViewTest::new_board_view(&sdk, view_data.to_vec()).await
|
||||
}
|
||||
DatabaseViewLayout::Calendar => {
|
||||
let build_context = make_test_calendar();
|
||||
let view_data: Bytes = build_context.into();
|
||||
ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await
|
||||
}
|
||||
};
|
||||
|
||||
let editor = sdk.grid_manager.open_database(&test.view.id).await.unwrap();
|
||||
let field_revs = editor.get_field_revs(None).await.unwrap();
|
||||
let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
|
||||
let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap();
|
||||
assert_eq!(block_meta_revs.len(), 1);
|
||||
|
||||
// It seems like you should add the field in the make_test_grid() function.
|
||||
// Because we assert the initialize count of the fields is equal to FieldType::COUNT.
|
||||
assert_eq!(field_revs.len(), FieldType::COUNT);
|
||||
|
||||
let grid_id = test.view.id;
|
||||
Self {
|
||||
sdk,
|
||||
view_id: grid_id,
|
||||
editor,
|
||||
field_revs,
|
||||
block_meta_revs,
|
||||
row_revs: row_pbs,
|
||||
field_count: FieldType::COUNT,
|
||||
row_by_row_id: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
|
||||
self.editor.get_all_row_revs(&self.view_id).await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn grid_filters(&self) -> Vec<FilterPB> {
|
||||
self.editor.get_all_filters().await.unwrap()
|
||||
}
|
||||
|
||||
pub fn get_field_rev(&self, field_id: &str, field_type: FieldType) -> &Arc<FieldRevision> {
|
||||
self.field_revs
|
||||
.iter()
|
||||
.filter(|field_rev| {
|
||||
let t_field_type: FieldType = field_rev.ty.into();
|
||||
field_rev.id == field_id && t_field_type == field_type
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.pop()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// returns the first `FieldRevision` in the build-in test grid.
|
||||
/// Not support duplicate `FieldType` in test grid yet.
|
||||
pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc<FieldRevision> {
|
||||
self.field_revs
|
||||
.iter()
|
||||
.filter(|field_rev| {
|
||||
let t_field_type: FieldType = field_rev.ty.into();
|
||||
t_field_type == field_type
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.pop()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec<SelectOptionPB> {
|
||||
let field_type = FieldType::MultiSelect;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
let type_option = field_rev
|
||||
.get_type_option::<MultiSelectTypeOptionPB>(field_type.into())
|
||||
.unwrap();
|
||||
type_option.options
|
||||
}
|
||||
|
||||
pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOptionPB {
|
||||
let field_type = FieldType::SingleSelect;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
let type_option = field_rev
|
||||
.get_type_option::<SingleSelectTypeOptionPB>(field_type.into())
|
||||
.unwrap();
|
||||
type_option
|
||||
}
|
||||
|
||||
pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB {
|
||||
let field_type = FieldType::Checklist;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
let type_option = field_rev
|
||||
.get_type_option::<ChecklistTypeOptionPB>(field_type.into())
|
||||
.unwrap();
|
||||
type_option
|
||||
}
|
||||
pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB {
|
||||
let field_type = FieldType::Checkbox;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
let type_option = field_rev
|
||||
.get_type_option::<CheckboxTypeOptionPB>(field_type.into())
|
||||
.unwrap();
|
||||
type_option
|
||||
}
|
||||
|
||||
pub fn block_id(&self) -> &str {
|
||||
&self.block_meta_revs.last().unwrap().block_id
|
||||
}
|
||||
|
||||
pub async fn update_cell<T: ToCellChangesetString>(&mut self, field_id: &str, row_id: String, cell_changeset: T) {
|
||||
let field_rev = self
|
||||
.field_revs
|
||||
.iter()
|
||||
.find(|field_rev| field_rev.id == field_id)
|
||||
.unwrap();
|
||||
|
||||
self.editor
|
||||
.update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) {
|
||||
let field_rev = self
|
||||
.field_revs
|
||||
.iter()
|
||||
.find(|field_rev| {
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
field_type == FieldType::RichText
|
||||
})
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
self.update_cell(&field_rev.id, row_id, content.to_string()).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) {
|
||||
let field_rev = self
|
||||
.field_revs
|
||||
.iter()
|
||||
.find(|field_rev| {
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
field_type == FieldType::SingleSelect
|
||||
})
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(&option_id);
|
||||
self.update_cell(&field_rev.id, row_id, cell_changeset).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub const GOOGLE: &str = "Google";
|
||||
pub const FACEBOOK: &str = "Facebook";
|
||||
pub const TWITTER: &str = "Twitter";
|
||||
|
||||
pub const COMPLETED: &str = "Completed";
|
||||
pub const PLANNED: &str = "Planned";
|
||||
pub const PAUSED: &str = "Paused";
|
||||
|
||||
pub const FIRST_THING: &str = "Wake up at 6:00 am";
|
||||
pub const SECOND_THING: &str = "Get some coffee";
|
||||
pub const THIRD_THING: &str = "Start working";
|
||||
|
||||
/// The build-in test data for grid. Currently, there are five rows in this grid, if you want to add
|
||||
/// more rows or alter the data in this grid. Some tests may fail. So you need to fix the failed tests.
|
||||
fn make_test_grid() -> BuildDatabaseContext {
|
||||
let mut grid_builder = DatabaseBuilder::new();
|
||||
// Iterate through the FieldType to create the corresponding Field.
|
||||
for field_type in FieldType::iter() {
|
||||
let field_type: FieldType = field_type;
|
||||
|
||||
// The
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.primary(true)
|
||||
.build();
|
||||
grid_builder.add_field(text_field);
|
||||
}
|
||||
FieldType::Number => {
|
||||
// Number
|
||||
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
|
||||
grid_builder.add_field(number_field);
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
// Date
|
||||
let date = DateTypeOptionBuilder::default()
|
||||
.date_format(DateFormat::US)
|
||||
.time_format(TimeFormat::TwentyFourHour);
|
||||
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
|
||||
grid_builder.add_field(date_field);
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
// Single Select
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(COMPLETED))
|
||||
.add_option(SelectOptionPB::new(PLANNED))
|
||||
.add_option(SelectOptionPB::new(PAUSED));
|
||||
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
|
||||
grid_builder.add_field(single_select_field);
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(GOOGLE))
|
||||
.add_option(SelectOptionPB::new(FACEBOOK))
|
||||
.add_option(SelectOptionPB::new(TWITTER));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
grid_builder.add_field(multi_select_field);
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
// Checkbox
|
||||
let checkbox = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
|
||||
grid_builder.add_field(checkbox_field);
|
||||
}
|
||||
FieldType::URL => {
|
||||
// URL
|
||||
let url = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
grid_builder.add_field(url_field);
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let checklist = ChecklistTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(FIRST_THING))
|
||||
.add_option(SelectOptionPB::new(SECOND_THING))
|
||||
.add_option(SelectOptionPB::new(THIRD_THING));
|
||||
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
|
||||
grid_builder.add_field(checklist_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..6 {
|
||||
let block_id = grid_builder.block_id().to_owned();
|
||||
let field_revs = grid_builder.field_revs();
|
||||
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
|
||||
match i {
|
||||
0 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("A"),
|
||||
FieldType::Number => row_builder.insert_number_cell("1"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
|
||||
FieldType::Checklist => row_builder.insert_checklist_cell(|options| options),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
FieldType::URL => row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell(""),
|
||||
FieldType::Number => row_builder.insert_number_cell("2"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("C"),
|
||||
FieldType::Number => row_builder.insert_number_cell("3"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(0))
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)])
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("DA"),
|
||||
FieldType::Number => row_builder.insert_number_cell("4"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(0))
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("AE"),
|
||||
FieldType::Number => row_builder.insert_number_cell(""),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
}
|
||||
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
5 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("AE"),
|
||||
FieldType::Number => row_builder.insert_number_cell("5"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1671938394"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let row_rev = row_builder.build();
|
||||
grid_builder.add_row(row_rev);
|
||||
}
|
||||
grid_builder.build()
|
||||
}
|
||||
|
||||
// Kanban board unit test mock data
|
||||
fn make_test_board() -> BuildDatabaseContext {
|
||||
let mut grid_builder = DatabaseBuilder::new();
|
||||
// Iterate through the FieldType to create the corresponding Field.
|
||||
for field_type in FieldType::iter() {
|
||||
let field_type: FieldType = field_type;
|
||||
|
||||
// The
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.primary(true)
|
||||
.build();
|
||||
grid_builder.add_field(text_field);
|
||||
}
|
||||
FieldType::Number => {
|
||||
// Number
|
||||
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
|
||||
grid_builder.add_field(number_field);
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
// Date
|
||||
let date = DateTypeOptionBuilder::default()
|
||||
.date_format(DateFormat::US)
|
||||
.time_format(TimeFormat::TwentyFourHour);
|
||||
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
|
||||
grid_builder.add_field(date_field);
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
// Single Select
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(COMPLETED))
|
||||
.add_option(SelectOptionPB::new(PLANNED))
|
||||
.add_option(SelectOptionPB::new(PAUSED));
|
||||
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
|
||||
grid_builder.add_field(single_select_field);
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(GOOGLE))
|
||||
.add_option(SelectOptionPB::new(FACEBOOK))
|
||||
.add_option(SelectOptionPB::new(TWITTER));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
grid_builder.add_field(multi_select_field);
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
// Checkbox
|
||||
let checkbox = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
|
||||
grid_builder.add_field(checkbox_field);
|
||||
}
|
||||
FieldType::URL => {
|
||||
// URL
|
||||
let url = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
grid_builder.add_field(url_field);
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let checklist = ChecklistTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(FIRST_THING))
|
||||
.add_option(SelectOptionPB::new(SECOND_THING))
|
||||
.add_option(SelectOptionPB::new(THIRD_THING));
|
||||
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
|
||||
grid_builder.add_field(checklist_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have many assumptions base on the number of the rows, so do not change the number of the loop.
|
||||
for i in 0..5 {
|
||||
let block_id = grid_builder.block_id().to_owned();
|
||||
let field_revs = grid_builder.field_revs();
|
||||
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
|
||||
match i {
|
||||
0 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("A"),
|
||||
FieldType::Number => row_builder.insert_number_cell("1"),
|
||||
// 1647251762 => Mar 14,2022
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(0))
|
||||
}
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("B"),
|
||||
FieldType::Number => row_builder.insert_number_cell("2"),
|
||||
// 1647251762 => Mar 14,2022
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(0))
|
||||
}
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("C"),
|
||||
FieldType::Number => row_builder.insert_number_cell("3"),
|
||||
// 1647251762 => Mar 14,2022
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
row_builder.insert_multi_select_cell(|mut options| vec![options.remove(0)])
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
FieldType::URL => row_builder.insert_url_cell("https://github.com/AppFlowy-IO/AppFlowy"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("DA"),
|
||||
FieldType::Number => row_builder.insert_number_cell("4"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("AE"),
|
||||
FieldType::Number => row_builder.insert_number_cell(""),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(2))
|
||||
}
|
||||
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let row_rev = row_builder.build();
|
||||
grid_builder.add_row(row_rev);
|
||||
}
|
||||
grid_builder.build()
|
||||
}
|
||||
|
||||
// Calendar unit test mock data
|
||||
fn make_test_calendar() -> BuildDatabaseContext {
|
||||
todo!()
|
||||
}
|
@ -1,381 +0,0 @@
|
||||
use crate::grid::script::EditorScript::*;
|
||||
use crate::grid::script::*;
|
||||
use chrono::NaiveDateTime;
|
||||
use flowy_database::services::field::{
|
||||
DateCellContentChangeset, DateCellData, MultiSelectTypeOptionPB, SelectOption, SelectOptionCellContentChangeset,
|
||||
SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
|
||||
};
|
||||
use flowy_database::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder};
|
||||
use grid_model::entities::{
|
||||
CellChangeset, FieldChangesetParams, FieldType, GridBlockInfoChangeset, GridBlockMetaSnapshot, RowMetaChangeset,
|
||||
TypeOptionDataFormat,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_field() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let (text_field_params, text_field_meta) = create_text_field(&test.grid_id);
|
||||
let (single_select_params, single_select_field) = create_single_select_field(&test.grid_id);
|
||||
let scripts = vec![
|
||||
CreateField {
|
||||
params: text_field_params,
|
||||
},
|
||||
AssertFieldEqual {
|
||||
field_index: test.field_count,
|
||||
field_meta: text_field_meta,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
|
||||
let scripts = vec![
|
||||
CreateField {
|
||||
params: single_select_params,
|
||||
},
|
||||
AssertFieldEqual {
|
||||
field_index: test.field_count,
|
||||
field_meta: single_select_field,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_duplicate_field() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let (params, _) = create_text_field(&test.grid_id);
|
||||
let field_count = test.field_count;
|
||||
let expected_field_count = field_count + 1;
|
||||
let scripts = vec![
|
||||
CreateField { params: params.clone() },
|
||||
CreateField { params },
|
||||
AssertFieldCount(expected_field_count),
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_field_with_empty_change() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let (params, field_meta) = create_single_select_field(&test.grid_id);
|
||||
let changeset = FieldChangesetParams {
|
||||
field_id: field_meta.id.clone(),
|
||||
grid_id: test.grid_id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let scripts = vec![
|
||||
CreateField { params },
|
||||
UpdateField { changeset },
|
||||
AssertFieldEqual {
|
||||
field_index: test.field_count,
|
||||
field_meta,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_field() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let (single_select_params, single_select_field) = create_single_select_field(&test.grid_id);
|
||||
let mut cloned_field = single_select_field.clone();
|
||||
|
||||
let mut single_select_type_option = SingleSelectTypeOption::from(&single_select_field);
|
||||
single_select_type_option.options.push(SelectOption::new("Unknown"));
|
||||
let changeset = FieldChangesetParams {
|
||||
field_id: single_select_field.id.clone(),
|
||||
grid_id: test.grid_id.clone(),
|
||||
frozen: Some(true),
|
||||
width: Some(1000),
|
||||
type_option_data: Some(single_select_type_option.protobuf_bytes().to_vec()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
cloned_field.frozen = true;
|
||||
cloned_field.width = 1000;
|
||||
cloned_field.insert_type_option_entry(&single_select_type_option);
|
||||
|
||||
let scripts = vec![
|
||||
CreateField {
|
||||
params: single_select_params,
|
||||
},
|
||||
UpdateField { changeset },
|
||||
AssertFieldEqual {
|
||||
field_index: test.field_count,
|
||||
field_meta: cloned_field,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_delete_field() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let expected_field_count = test.field_count;
|
||||
let (text_params, text_field) = create_text_field(&test.grid_id);
|
||||
let scripts = vec![
|
||||
CreateField { params: text_params },
|
||||
DeleteField { field_meta: text_field },
|
||||
AssertFieldCount(expected_field_count),
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_block() {
|
||||
let grid_block = GridBlockMetaSnapshot::new();
|
||||
let scripts = vec![
|
||||
AssertBlockCount(1),
|
||||
CreateBlock { block: grid_block },
|
||||
AssertBlockCount(2),
|
||||
];
|
||||
GridEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_block() {
|
||||
let grid_block = GridBlockMetaSnapshot::new();
|
||||
let mut cloned_grid_block = grid_block.clone();
|
||||
let changeset = GridBlockInfoChangeset {
|
||||
block_id: grid_block.block_id.clone(),
|
||||
start_row_index: Some(2),
|
||||
row_count: Some(10),
|
||||
};
|
||||
|
||||
cloned_grid_block.start_row_index = 2;
|
||||
cloned_grid_block.row_count = 10;
|
||||
|
||||
let scripts = vec![
|
||||
AssertBlockCount(1),
|
||||
CreateBlock { block: grid_block },
|
||||
UpdateBlock { changeset },
|
||||
AssertBlockCount(2),
|
||||
AssertBlockEqual {
|
||||
block_index: 1,
|
||||
block: cloned_grid_block,
|
||||
},
|
||||
];
|
||||
GridEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_row() {
|
||||
let scripts = vec![AssertRowCount(3), CreateEmptyRow, CreateEmptyRow, AssertRowCount(5)];
|
||||
GridEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_row2() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let create_row_context = CreateRowMetaBuilder::new(&test.field_metas).build();
|
||||
let scripts = vec![
|
||||
AssertRowCount(3),
|
||||
CreateRow {
|
||||
context: create_row_context,
|
||||
},
|
||||
AssertRowCount(4),
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_row() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let context = CreateRowMetaBuilder::new(&test.field_metas).build();
|
||||
let changeset = RowMetaChangeset {
|
||||
row_id: context.row_id.clone(),
|
||||
height: None,
|
||||
visibility: None,
|
||||
cell_by_field_id: Default::default(),
|
||||
};
|
||||
|
||||
let scripts = vec![
|
||||
AssertRowCount(3),
|
||||
CreateRow { context },
|
||||
UpdateRow {
|
||||
changeset: changeset.clone(),
|
||||
},
|
||||
AssertRow { changeset },
|
||||
AssertRowCount(4),
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_delete_row() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let context_1 = CreateRowMetaBuilder::new(&test.field_metas).build();
|
||||
let context_2 = CreateRowMetaBuilder::new(&test.field_metas).build();
|
||||
let row_ids = vec![context_1.row_id.clone(), context_2.row_id.clone()];
|
||||
let scripts = vec![
|
||||
AssertRowCount(3),
|
||||
CreateRow { context: context_1 },
|
||||
CreateRow { context: context_2 },
|
||||
AssertBlockCount(1),
|
||||
AssertBlock {
|
||||
block_index: 0,
|
||||
row_count: 5,
|
||||
start_row_index: 0,
|
||||
},
|
||||
DeleteRow { row_ids },
|
||||
AssertBlock {
|
||||
block_index: 0,
|
||||
row_count: 3,
|
||||
start_row_index: 0,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_row_add_cells_test() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let mut builder = CreateRowMetaBuilder::new(&test.field_metas);
|
||||
for field in &test.field_metas {
|
||||
match field.field_type {
|
||||
FieldType::RichText => {
|
||||
builder.add_cell(&field.id, "hello world".to_owned()).unwrap();
|
||||
}
|
||||
FieldType::Number => {
|
||||
builder.add_cell(&field.id, "18,443".to_owned()).unwrap();
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
builder
|
||||
.add_cell(&field.id, make_date_cell_string("1647251762"))
|
||||
.unwrap();
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
let type_option = SingleSelectTypeOption::from(field);
|
||||
let option = type_option.options.first().unwrap();
|
||||
builder.add_select_option_cell(&field.id, option.id.clone()).unwrap();
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let type_option = MultiSelectTypeOptionPB::from(field);
|
||||
let ops_ids = type_option
|
||||
.options
|
||||
.iter()
|
||||
.map(|option| option.id.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join(SELECTION_IDS_SEPARATOR);
|
||||
builder.add_select_option_cell(&field.id, ops_ids).unwrap();
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
builder.add_cell(&field.id, "false".to_string()).unwrap();
|
||||
}
|
||||
FieldType::URL => {
|
||||
builder.add_cell(&field.id, "1".to_string()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
let context = builder.build();
|
||||
let scripts = vec![CreateRow { context }, AssertGridMetaPad];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_row_add_date_cell_test() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let mut builder = CreateRowMetaBuilder::new(&test.field_metas);
|
||||
let mut date_field = None;
|
||||
let timestamp = 1647390674;
|
||||
for field in &test.field_metas {
|
||||
if field.field_type == FieldType::DateTime {
|
||||
date_field = Some(field.clone());
|
||||
NaiveDateTime::from_timestamp(123, 0);
|
||||
// The data should not be empty
|
||||
assert!(builder.add_cell(&field.id, "".to_string()).is_err());
|
||||
assert!(builder.add_cell(&field.id, make_date_cell_string("123")).is_ok());
|
||||
assert!(builder
|
||||
.add_cell(&field.id, make_date_cell_string(×tamp.to_string()))
|
||||
.is_ok());
|
||||
}
|
||||
}
|
||||
let context = builder.build();
|
||||
let date_field = date_field.unwrap();
|
||||
let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
|
||||
assert_eq!(
|
||||
decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type)
|
||||
.parse::<DateCellData>()
|
||||
.unwrap()
|
||||
.date,
|
||||
"2022/03/16",
|
||||
);
|
||||
let scripts = vec![CreateRow { context }];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_cell_update() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let field_metas = &test.field_metas;
|
||||
let row_metas = &test.row_metas;
|
||||
let grid_blocks = &test.grid_blocks;
|
||||
assert_eq!(row_metas.len(), 3);
|
||||
assert_eq!(grid_blocks.len(), 1);
|
||||
|
||||
let block_id = &grid_blocks.first().unwrap().block_id;
|
||||
let mut scripts = vec![];
|
||||
for (index, row_meta) in row_metas.iter().enumerate() {
|
||||
for field_meta in field_metas {
|
||||
if index == 0 {
|
||||
let data = match field_meta.field_type {
|
||||
FieldType::RichText => "".to_string(),
|
||||
FieldType::Number => "123".to_string(),
|
||||
FieldType::DateTime => make_date_cell_string("123"),
|
||||
FieldType::SingleSelect => {
|
||||
let type_option = SingleSelectTypeOption::from(field_meta);
|
||||
SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let type_option = MultiSelectTypeOptionPB::from(field_meta);
|
||||
SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
|
||||
}
|
||||
FieldType::Checkbox => "1".to_string(),
|
||||
FieldType::URL => "1".to_string(),
|
||||
};
|
||||
|
||||
scripts.push(UpdateCell {
|
||||
changeset: CellChangeset {
|
||||
database_id: block_id.to_string(),
|
||||
row_id: row_meta.id.clone(),
|
||||
field_id: field_meta.id.clone(),
|
||||
cell_content_changeset: Some(data),
|
||||
},
|
||||
is_err: false,
|
||||
});
|
||||
}
|
||||
|
||||
if index == 1 {
|
||||
let (data, is_err) = match field_meta.field_type {
|
||||
FieldType::RichText => ("1".to_string().repeat(10001), true),
|
||||
FieldType::Number => ("abc".to_string(), true),
|
||||
FieldType::DateTime => ("abc".to_string(), true),
|
||||
FieldType::SingleSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false),
|
||||
FieldType::MultiSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false),
|
||||
FieldType::Checkbox => ("2".to_string(), false),
|
||||
FieldType::URL => ("2".to_string(), false),
|
||||
};
|
||||
|
||||
scripts.push(UpdateCell {
|
||||
changeset: CellChangeset {
|
||||
database_id: block_id.to_string(),
|
||||
row_id: row_meta.id.clone(),
|
||||
field_id: field_meta.id.clone(),
|
||||
cell_content_changeset: Some(data),
|
||||
},
|
||||
is_err,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
fn make_date_cell_string(s: &str) -> String {
|
||||
serde_json::to_string(&DateCellContentChangeset {
|
||||
date: Some(s.to_string()),
|
||||
time: None,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
mod script;
|
||||
mod test;
|
||||
mod url_group_test;
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
use crate::grid::database_editor::DatabaseEditorTest;
|
||||
use flowy_database::entities::{
|
||||
CreateRowParams, DatabaseViewLayout, FieldType, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB,
|
||||
};
|
||||
use flowy_database::services::cell::{delete_select_option_cell, insert_select_option_cell};
|
||||
use flowy_database::services::cell::{delete_select_option_cell, insert_select_option_cell, insert_url_cell};
|
||||
use flowy_database::services::field::{
|
||||
edit_single_select_type_option, SelectOptionPB, SelectTypeOptionSharedAction, SingleSelectTypeOptionPB,
|
||||
};
|
||||
@ -37,11 +37,16 @@ pub enum GroupScript {
|
||||
group_index: usize,
|
||||
row_index: usize,
|
||||
},
|
||||
UpdateRow {
|
||||
UpdateGroupedCell {
|
||||
from_group_index: usize,
|
||||
row_index: usize,
|
||||
to_group_index: usize,
|
||||
},
|
||||
UpdateGroupedCellWithData {
|
||||
from_group_index: usize,
|
||||
row_index: usize,
|
||||
cell_data: String,
|
||||
},
|
||||
MoveGroup {
|
||||
from_group_index: usize,
|
||||
to_group_index: usize,
|
||||
@ -54,13 +59,13 @@ pub enum GroupScript {
|
||||
},
|
||||
}
|
||||
|
||||
pub struct GridGroupTest {
|
||||
inner: GridEditorTest,
|
||||
pub struct DatabaseGroupTest {
|
||||
inner: DatabaseEditorTest,
|
||||
}
|
||||
|
||||
impl GridGroupTest {
|
||||
impl DatabaseGroupTest {
|
||||
pub async fn new() -> Self {
|
||||
let editor_test = GridEditorTest::new_board().await;
|
||||
let editor_test = DatabaseEditorTest::new_board().await;
|
||||
Self { inner: editor_test }
|
||||
}
|
||||
|
||||
@ -109,7 +114,6 @@ impl GridGroupTest {
|
||||
assert_eq!(row.id, compare_row.id);
|
||||
}
|
||||
GroupScript::CreateRow { group_index } => {
|
||||
//
|
||||
let group = self.group_at_index(group_index).await;
|
||||
let params = CreateRowParams {
|
||||
database_id: self.editor.database_id.clone(),
|
||||
@ -123,7 +127,7 @@ impl GridGroupTest {
|
||||
let row = self.row_at_index(group_index, row_index).await;
|
||||
self.editor.delete_row(&row.id).await.unwrap();
|
||||
}
|
||||
GroupScript::UpdateRow {
|
||||
GroupScript::UpdateGroupedCell {
|
||||
from_group_index,
|
||||
row_index,
|
||||
to_group_index,
|
||||
@ -154,6 +158,7 @@ impl GridGroupTest {
|
||||
FieldType::MultiSelect => {
|
||||
insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev)
|
||||
}
|
||||
FieldType::URL => insert_url_cell(to_group.group_id.clone(), &field_rev),
|
||||
_ => {
|
||||
panic!("Unsupported group field type");
|
||||
}
|
||||
@ -165,6 +170,27 @@ impl GridGroupTest {
|
||||
row_changeset.cell_by_field_id.insert(field_id, cell_rev);
|
||||
self.editor.update_row(row_changeset).await.unwrap();
|
||||
}
|
||||
GroupScript::UpdateGroupedCellWithData {
|
||||
from_group_index,
|
||||
row_index,
|
||||
cell_data,
|
||||
} => {
|
||||
let from_group = self.group_at_index(from_group_index).await;
|
||||
let field_id = from_group.field_id;
|
||||
let field_rev = self.editor.get_field_rev(&field_id).await.unwrap();
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
let cell_rev = match field_type {
|
||||
FieldType::URL => insert_url_cell(cell_data, &field_rev),
|
||||
_ => {
|
||||
panic!("Unsupported group field type");
|
||||
}
|
||||
};
|
||||
|
||||
let row_id = self.row_at_index(from_group_index, row_index).await.id;
|
||||
let mut row_changeset = RowChangeset::new(row_id);
|
||||
row_changeset.cell_by_field_id.insert(field_id, cell_rev);
|
||||
self.editor.update_row(row_changeset).await.unwrap();
|
||||
}
|
||||
GroupScript::MoveGroup {
|
||||
from_group_index,
|
||||
to_group_index,
|
||||
@ -258,15 +284,15 @@ impl GridGroupTest {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for GridGroupTest {
|
||||
type Target = GridEditorTest;
|
||||
impl std::ops::Deref for DatabaseGroupTest {
|
||||
type Target = DatabaseEditorTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for GridGroupTest {
|
||||
impl std::ops::DerefMut for DatabaseGroupTest {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::grid::group_test::script::GridGroupTest;
|
||||
use crate::grid::group_test::script::DatabaseGroupTest;
|
||||
use crate::grid::group_test::script::GroupScript::*;
|
||||
|
||||
use flowy_database::services::field::SelectOptionPB;
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_init_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let scripts = vec![
|
||||
AssertGroupCount(4),
|
||||
AssertGroupRowCount {
|
||||
@ -30,7 +30,7 @@ async fn group_init_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_row_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let group = test.group_at_index(1).await;
|
||||
let scripts = vec![
|
||||
// Move the row at 0 in group0 to group1 at 1
|
||||
@ -55,7 +55,7 @@ async fn group_move_row_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_row_to_other_group_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let group = test.group_at_index(1).await;
|
||||
let scripts = vec![
|
||||
MoveRow {
|
||||
@ -83,7 +83,7 @@ async fn group_move_row_to_other_group_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_two_row_to_other_group_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let group_1 = test.group_at_index(1).await;
|
||||
let scripts = vec![
|
||||
// Move row at index 0 from group 1 to group 2 at index 1
|
||||
@ -137,7 +137,7 @@ async fn group_move_two_row_to_other_group_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_row_to_other_group_and_reorder_from_up_to_down_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let group_1 = test.group_at_index(1).await;
|
||||
let group_2 = test.group_at_index(2).await;
|
||||
let scripts = vec![
|
||||
@ -173,7 +173,7 @@ async fn group_move_row_to_other_group_and_reorder_from_up_to_down_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_row_to_other_group_and_reorder_from_bottom_to_up_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let scripts = vec![MoveRow {
|
||||
from_group_index: 1,
|
||||
from_row_index: 0,
|
||||
@ -204,7 +204,7 @@ async fn group_move_row_to_other_group_and_reorder_from_bottom_to_up_test() {
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn group_create_row_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateRow { group_index: 1 },
|
||||
AssertGroupRowCount {
|
||||
@ -223,7 +223,7 @@ async fn group_create_row_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_delete_row_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let scripts = vec![
|
||||
DeleteRow {
|
||||
group_index: 1,
|
||||
@ -239,7 +239,7 @@ async fn group_delete_row_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_delete_all_row_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let scripts = vec![
|
||||
DeleteRow {
|
||||
group_index: 1,
|
||||
@ -259,10 +259,10 @@ async fn group_delete_all_row_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_update_row_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let scripts = vec![
|
||||
// Update the row at 0 in group0 by setting the row's group field data
|
||||
UpdateRow {
|
||||
UpdateGroupedCell {
|
||||
from_group_index: 1,
|
||||
row_index: 0,
|
||||
to_group_index: 2,
|
||||
@ -281,10 +281,10 @@ async fn group_update_row_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_reorder_group_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let scripts = vec![
|
||||
// Update the row at 0 in group0 by setting the row's group field data
|
||||
UpdateRow {
|
||||
UpdateGroupedCell {
|
||||
from_group_index: 1,
|
||||
row_index: 0,
|
||||
to_group_index: 2,
|
||||
@ -303,9 +303,9 @@ async fn group_reorder_group_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_to_default_group_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let scripts = vec![
|
||||
UpdateRow {
|
||||
UpdateGroupedCell {
|
||||
from_group_index: 1,
|
||||
row_index: 0,
|
||||
to_group_index: 0,
|
||||
@ -324,10 +324,10 @@ async fn group_move_to_default_group_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_from_default_group_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
// Move one row from group 1 to group 0
|
||||
let scripts = vec![
|
||||
UpdateRow {
|
||||
UpdateGroupedCell {
|
||||
from_group_index: 1,
|
||||
row_index: 0,
|
||||
to_group_index: 0,
|
||||
@ -345,7 +345,7 @@ async fn group_move_from_default_group_test() {
|
||||
|
||||
// Move one row from group 0 to group 1
|
||||
let scripts = vec![
|
||||
UpdateRow {
|
||||
UpdateGroupedCell {
|
||||
from_group_index: 0,
|
||||
row_index: 0,
|
||||
to_group_index: 1,
|
||||
@ -364,7 +364,7 @@ async fn group_move_from_default_group_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_group_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let group_0 = test.group_at_index(0).await;
|
||||
let group_1 = test.group_at_index(1).await;
|
||||
let scripts = vec![
|
||||
@ -394,7 +394,7 @@ async fn group_move_group_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_group_row_after_move_group_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let group_1 = test.group_at_index(1).await;
|
||||
let group_2 = test.group_at_index(2).await;
|
||||
let scripts = vec![
|
||||
@ -430,7 +430,7 @@ async fn group_move_group_row_after_move_group_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_group_to_default_group_pos_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let group_0 = test.group_at_index(0).await;
|
||||
let group_3 = test.group_at_index(3).await;
|
||||
let scripts = vec![
|
||||
@ -452,7 +452,7 @@ async fn group_move_group_to_default_group_pos_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_insert_single_select_option_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let new_option_name = "New option";
|
||||
let scripts = vec![
|
||||
AssertGroupCount(4),
|
||||
@ -468,7 +468,7 @@ async fn group_insert_single_select_option_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_group_by_other_field() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let multi_select_field = test.get_multi_select_field().await;
|
||||
let scripts = vec![
|
||||
GroupByField {
|
||||
@ -486,52 +486,3 @@ async fn group_group_by_other_field() {
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_group_by_url() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let url_field = test.get_url_field().await;
|
||||
let scripts = vec![
|
||||
GroupByField {
|
||||
field_id: url_field.id.clone(),
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 0,
|
||||
row_count: 2,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 2,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 2,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupCount(3),
|
||||
MoveRow {
|
||||
from_group_index: 0,
|
||||
from_row_index: 0,
|
||||
to_group_index: 1,
|
||||
to_row_index: 0,
|
||||
},
|
||||
MoveRow {
|
||||
from_group_index: 1,
|
||||
from_row_index: 0,
|
||||
to_group_index: 2,
|
||||
to_row_index: 0,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 0,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 2,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 2,
|
||||
row_count: 2,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
@ -0,0 +1,148 @@
|
||||
use crate::grid::group_test::script::DatabaseGroupTest;
|
||||
use crate::grid::group_test::script::GroupScript::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_group_by_url() {
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let url_field = test.get_url_field().await;
|
||||
let scripts = vec![
|
||||
GroupByField {
|
||||
field_id: url_field.id.clone(),
|
||||
},
|
||||
// no status group
|
||||
AssertGroupRowCount {
|
||||
group_index: 0,
|
||||
row_count: 2,
|
||||
},
|
||||
// https://appflowy.io
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 2,
|
||||
},
|
||||
// https://github.com/AppFlowy-IO/AppFlowy
|
||||
AssertGroupRowCount {
|
||||
group_index: 2,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupCount(3),
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_alter_url_to_another_group_url_test() {
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let url_field = test.get_url_field().await;
|
||||
let scripts = vec![
|
||||
GroupByField {
|
||||
field_id: url_field.id.clone(),
|
||||
},
|
||||
// no status group
|
||||
AssertGroupRowCount {
|
||||
group_index: 0,
|
||||
row_count: 2,
|
||||
},
|
||||
// https://appflowy.io
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 2,
|
||||
},
|
||||
// https://github.com/AppFlowy-IO/AppFlowy
|
||||
AssertGroupRowCount {
|
||||
group_index: 2,
|
||||
row_count: 1,
|
||||
},
|
||||
// When moving the last row from 2nd group to 1nd group, the 2nd group will be removed
|
||||
UpdateGroupedCell {
|
||||
from_group_index: 2,
|
||||
row_index: 0,
|
||||
to_group_index: 1,
|
||||
},
|
||||
AssertGroupCount(2),
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_alter_url_to_new_url_test() {
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let url_field = test.get_url_field().await;
|
||||
let scripts = vec![
|
||||
GroupByField {
|
||||
field_id: url_field.id.clone(),
|
||||
},
|
||||
// When moving the last row from 2nd group to 1nd group, the 2nd group will be removed
|
||||
UpdateGroupedCellWithData {
|
||||
from_group_index: 0,
|
||||
row_index: 0,
|
||||
cell_data: "https://github.com/AppFlowy-IO".to_string(),
|
||||
},
|
||||
// no status group
|
||||
AssertGroupRowCount {
|
||||
group_index: 0,
|
||||
row_count: 1,
|
||||
},
|
||||
// https://appflowy.io
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 2,
|
||||
},
|
||||
// https://github.com/AppFlowy-IO/AppFlowy
|
||||
AssertGroupRowCount {
|
||||
group_index: 2,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 3,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupCount(4),
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_url_group_row_test() {
|
||||
let mut test = DatabaseGroupTest::new().await;
|
||||
let url_field = test.get_url_field().await;
|
||||
let scripts = vec![
|
||||
GroupByField {
|
||||
field_id: url_field.id.clone(),
|
||||
},
|
||||
// no status group
|
||||
AssertGroupRowCount {
|
||||
group_index: 0,
|
||||
row_count: 2,
|
||||
},
|
||||
// https://appflowy.io
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 2,
|
||||
},
|
||||
// https://github.com/AppFlowy-IO/AppFlowy
|
||||
AssertGroupRowCount {
|
||||
group_index: 2,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupCount(3),
|
||||
MoveRow {
|
||||
from_group_index: 0,
|
||||
from_row_index: 0,
|
||||
to_group_index: 1,
|
||||
to_row_index: 0,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 0,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 3,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 2,
|
||||
row_count: 1,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
// #![allow(clippy::all)]
|
||||
// #![allow(dead_code)]
|
||||
// #![allow(unused_imports)]
|
||||
use crate::grid::block_test::util::GridRowTestBuilder;
|
||||
use crate::grid::mock_data::{
|
||||
COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER,
|
||||
};
|
||||
|
||||
use flowy_client_sync::client_database::DatabaseBuilder;
|
||||
use flowy_database::entities::*;
|
||||
|
||||
use flowy_database::services::field::SelectOptionPB;
|
||||
use flowy_database::services::field::*;
|
||||
|
||||
use grid_model::*;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
// Kanban board unit test mock data
|
||||
pub fn make_test_board() -> BuildDatabaseContext {
|
||||
let mut grid_builder = DatabaseBuilder::new();
|
||||
// Iterate through the FieldType to create the corresponding Field.
|
||||
for field_type in FieldType::iter() {
|
||||
let field_type: FieldType = field_type;
|
||||
|
||||
// The
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.primary(true)
|
||||
.build();
|
||||
grid_builder.add_field(text_field);
|
||||
}
|
||||
FieldType::Number => {
|
||||
// Number
|
||||
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
|
||||
grid_builder.add_field(number_field);
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
// Date
|
||||
let date = DateTypeOptionBuilder::default()
|
||||
.date_format(DateFormat::US)
|
||||
.time_format(TimeFormat::TwentyFourHour);
|
||||
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
|
||||
grid_builder.add_field(date_field);
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
// Single Select
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(COMPLETED))
|
||||
.add_option(SelectOptionPB::new(PLANNED))
|
||||
.add_option(SelectOptionPB::new(PAUSED));
|
||||
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
|
||||
grid_builder.add_field(single_select_field);
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(GOOGLE))
|
||||
.add_option(SelectOptionPB::new(FACEBOOK))
|
||||
.add_option(SelectOptionPB::new(TWITTER));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
grid_builder.add_field(multi_select_field);
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
// Checkbox
|
||||
let checkbox = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
|
||||
grid_builder.add_field(checkbox_field);
|
||||
}
|
||||
FieldType::URL => {
|
||||
// URL
|
||||
let url = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
grid_builder.add_field(url_field);
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let checklist = ChecklistTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(FIRST_THING))
|
||||
.add_option(SelectOptionPB::new(SECOND_THING))
|
||||
.add_option(SelectOptionPB::new(THIRD_THING));
|
||||
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
|
||||
grid_builder.add_field(checklist_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have many assumptions base on the number of the rows, so do not change the number of the loop.
|
||||
for i in 0..5 {
|
||||
let block_id = grid_builder.block_id().to_owned();
|
||||
let field_revs = grid_builder.field_revs();
|
||||
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
|
||||
match i {
|
||||
0 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("A"),
|
||||
FieldType::Number => row_builder.insert_number_cell("1"),
|
||||
// 1647251762 => Mar 14,2022
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(0))
|
||||
}
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("B"),
|
||||
FieldType::Number => row_builder.insert_number_cell("2"),
|
||||
// 1647251762 => Mar 14,2022
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(0))
|
||||
}
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("C"),
|
||||
FieldType::Number => row_builder.insert_number_cell("3"),
|
||||
// 1647251762 => Mar 14,2022
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
row_builder.insert_multi_select_cell(|mut options| vec![options.remove(0)])
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
FieldType::URL => row_builder.insert_url_cell("https://github.com/AppFlowy-IO/AppFlowy"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("DA"),
|
||||
FieldType::Number => row_builder.insert_number_cell("4"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("AE"),
|
||||
FieldType::Number => row_builder.insert_number_cell(""),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(2))
|
||||
}
|
||||
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let row_rev = row_builder.build();
|
||||
grid_builder.add_row(row_rev);
|
||||
}
|
||||
grid_builder.build()
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
use grid_model::BuildDatabaseContext;
|
||||
|
||||
// Calendar unit test mock data
|
||||
pub fn make_test_calendar() -> BuildDatabaseContext {
|
||||
todo!()
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
// #![allow(clippy::all)]
|
||||
// #![allow(dead_code)]
|
||||
// #![allow(unused_imports)]
|
||||
use crate::grid::block_test::util::GridRowTestBuilder;
|
||||
use crate::grid::mock_data::{
|
||||
COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER,
|
||||
};
|
||||
|
||||
use flowy_client_sync::client_database::DatabaseBuilder;
|
||||
use flowy_database::entities::*;
|
||||
|
||||
use flowy_database::services::field::SelectOptionPB;
|
||||
use flowy_database::services::field::*;
|
||||
|
||||
use grid_model::*;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
pub fn make_test_grid() -> BuildDatabaseContext {
|
||||
let mut grid_builder = DatabaseBuilder::new();
|
||||
// Iterate through the FieldType to create the corresponding Field.
|
||||
for field_type in FieldType::iter() {
|
||||
let field_type: FieldType = field_type;
|
||||
|
||||
// The
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.primary(true)
|
||||
.build();
|
||||
grid_builder.add_field(text_field);
|
||||
}
|
||||
FieldType::Number => {
|
||||
// Number
|
||||
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
|
||||
grid_builder.add_field(number_field);
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
// Date
|
||||
let date = DateTypeOptionBuilder::default()
|
||||
.date_format(DateFormat::US)
|
||||
.time_format(TimeFormat::TwentyFourHour);
|
||||
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
|
||||
grid_builder.add_field(date_field);
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
// Single Select
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(COMPLETED))
|
||||
.add_option(SelectOptionPB::new(PLANNED))
|
||||
.add_option(SelectOptionPB::new(PAUSED));
|
||||
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
|
||||
grid_builder.add_field(single_select_field);
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(GOOGLE))
|
||||
.add_option(SelectOptionPB::new(FACEBOOK))
|
||||
.add_option(SelectOptionPB::new(TWITTER));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
grid_builder.add_field(multi_select_field);
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
// Checkbox
|
||||
let checkbox = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
|
||||
grid_builder.add_field(checkbox_field);
|
||||
}
|
||||
FieldType::URL => {
|
||||
// URL
|
||||
let url = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
grid_builder.add_field(url_field);
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let checklist = ChecklistTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(FIRST_THING))
|
||||
.add_option(SelectOptionPB::new(SECOND_THING))
|
||||
.add_option(SelectOptionPB::new(THIRD_THING));
|
||||
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
|
||||
grid_builder.add_field(checklist_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..6 {
|
||||
let block_id = grid_builder.block_id().to_owned();
|
||||
let field_revs = grid_builder.field_revs();
|
||||
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
|
||||
match i {
|
||||
0 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("A"),
|
||||
FieldType::Number => row_builder.insert_number_cell("1"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
|
||||
FieldType::Checklist => row_builder.insert_checklist_cell(|options| options),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
FieldType::URL => row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell(""),
|
||||
FieldType::Number => row_builder.insert_number_cell("2"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("C"),
|
||||
FieldType::Number => row_builder.insert_number_cell("3"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(0))
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)])
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("DA"),
|
||||
FieldType::Number => row_builder.insert_number_cell("4"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(0))
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("AE"),
|
||||
FieldType::Number => row_builder.insert_number_cell(""),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
}
|
||||
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
5 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("AE"),
|
||||
FieldType::Number => row_builder.insert_number_cell("5"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1671938394"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
}
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let row_rev = row_builder.build();
|
||||
grid_builder.add_row(row_rev);
|
||||
}
|
||||
grid_builder.build()
|
||||
}
|
19
frontend/rust-lib/flowy-database/tests/grid/mock_data/mod.rs
Normal file
19
frontend/rust-lib/flowy-database/tests/grid/mock_data/mod.rs
Normal file
@ -0,0 +1,19 @@
|
||||
mod board_mock_data;
|
||||
mod calendar_mock_data;
|
||||
mod grid_mock_data;
|
||||
|
||||
pub use board_mock_data::*;
|
||||
pub use calendar_mock_data::*;
|
||||
pub use grid_mock_data::*;
|
||||
|
||||
pub const GOOGLE: &str = "Google";
|
||||
pub const FACEBOOK: &str = "Facebook";
|
||||
pub const TWITTER: &str = "Twitter";
|
||||
|
||||
pub const COMPLETED: &str = "Completed";
|
||||
pub const PLANNED: &str = "Planned";
|
||||
pub const PAUSED: &str = "Paused";
|
||||
|
||||
pub const FIRST_THING: &str = "Wake up at 6:00 am";
|
||||
pub const SECOND_THING: &str = "Get some coffee";
|
||||
pub const THIRD_THING: &str = "Start working";
|
@ -1,8 +1,10 @@
|
||||
mod block_test;
|
||||
mod cell_test;
|
||||
mod database_editor;
|
||||
mod field_test;
|
||||
mod filter_test;
|
||||
mod grid_editor;
|
||||
mod group_test;
|
||||
mod snapshot_test;
|
||||
mod sort_test;
|
||||
|
||||
mod mock_data;
|
||||
|
@ -1,374 +0,0 @@
|
||||
use bytes::Bytes;
|
||||
use flowy_client_sync::client_grid::GridBuilder;
|
||||
use flowy_database::services::field::*;
|
||||
use flowy_database::services::grid_meta_editor::{GridMetaEditor, GridPadBuilder};
|
||||
use flowy_database::services::row::CreateRowMetaPayload;
|
||||
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
|
||||
use flowy_test::helper::ViewTest;
|
||||
use flowy_test::FlowySDKTest;
|
||||
use grid_model::entities::{
|
||||
BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType,
|
||||
GridBlockInfoChangeset, GridBlockMetaSnapshot, InsertFieldParams, RowMeta, RowMetaChangeset, RowOrder,
|
||||
TypeOptionDataFormat,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use strum::EnumCount;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub enum EditorScript {
|
||||
CreateField {
|
||||
params: InsertFieldParams,
|
||||
},
|
||||
UpdateField {
|
||||
changeset: FieldChangesetParams,
|
||||
},
|
||||
DeleteField {
|
||||
field_meta: FieldMeta,
|
||||
},
|
||||
AssertFieldCount(usize),
|
||||
AssertFieldEqual {
|
||||
field_index: usize,
|
||||
field_meta: FieldMeta,
|
||||
},
|
||||
CreateBlock {
|
||||
block: GridBlockMetaSnapshot,
|
||||
},
|
||||
UpdateBlock {
|
||||
changeset: GridBlockInfoChangeset,
|
||||
},
|
||||
AssertBlockCount(usize),
|
||||
AssertBlock {
|
||||
block_index: usize,
|
||||
row_count: i32,
|
||||
start_row_index: i32,
|
||||
},
|
||||
AssertBlockEqual {
|
||||
block_index: usize,
|
||||
block: GridBlockMetaSnapshot,
|
||||
},
|
||||
CreateEmptyRow,
|
||||
CreateRow {
|
||||
context: CreateRowMetaPayload,
|
||||
},
|
||||
UpdateRow {
|
||||
changeset: RowMetaChangeset,
|
||||
},
|
||||
AssertRow {
|
||||
changeset: RowMetaChangeset,
|
||||
},
|
||||
DeleteRow {
|
||||
row_ids: Vec<String>,
|
||||
},
|
||||
UpdateCell {
|
||||
changeset: CellChangeset,
|
||||
is_err: bool,
|
||||
},
|
||||
AssertRowCount(usize),
|
||||
// AssertRowEqual{ row_index: usize, row: RowMeta},
|
||||
AssertGridMetaPad,
|
||||
}
|
||||
|
||||
pub struct GridEditorTest {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub grid_id: String,
|
||||
pub editor: Arc<GridMetaEditor>,
|
||||
pub field_metas: Vec<FieldMeta>,
|
||||
pub grid_blocks: Vec<GridBlockMetaSnapshot>,
|
||||
pub row_metas: Vec<Arc<RowMeta>>,
|
||||
pub field_count: usize,
|
||||
|
||||
pub row_order_by_row_id: HashMap<String, RowOrder>,
|
||||
}
|
||||
|
||||
impl GridEditorTest {
|
||||
pub async fn new() -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let _ = sdk.init_user().await;
|
||||
let build_context = make_template_1_grid();
|
||||
let view_data: Bytes = build_context.into();
|
||||
let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await;
|
||||
let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
|
||||
let field_metas = editor.get_field_metas::<FieldOrder>(None).await.unwrap();
|
||||
let grid_blocks = editor.get_block_metas().await.unwrap();
|
||||
let row_metas = get_row_metas(&editor).await;
|
||||
|
||||
let grid_id = test.view.id;
|
||||
Self {
|
||||
sdk,
|
||||
grid_id,
|
||||
editor,
|
||||
field_metas,
|
||||
grid_blocks,
|
||||
row_metas,
|
||||
field_count: FieldType::COUNT,
|
||||
row_order_by_row_id: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_scripts(&mut self, scripts: Vec<EditorScript>) {
|
||||
for script in scripts {
|
||||
self.run_script(script).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_script(&mut self, script: EditorScript) {
|
||||
let grid_manager = self.sdk.grid_manager.clone();
|
||||
let pool = self.sdk.user_session.db_pool().unwrap();
|
||||
let rev_manager = self.editor.rev_manager();
|
||||
let _cache = rev_manager.revision_cache().await;
|
||||
|
||||
match script {
|
||||
EditorScript::CreateField { params } => {
|
||||
if !self.editor.contain_field(¶ms.field.id).await {
|
||||
self.field_count += 1;
|
||||
}
|
||||
|
||||
self.editor.insert_field(params).await.unwrap();
|
||||
self.field_metas = self.editor.get_field_metas::<FieldOrder>(None).await.unwrap();
|
||||
assert_eq!(self.field_count, self.field_metas.len());
|
||||
}
|
||||
EditorScript::UpdateField { changeset: change } => {
|
||||
self.editor.update_field(change).await.unwrap();
|
||||
self.field_metas = self.editor.get_field_metas::<FieldOrder>(None).await.unwrap();
|
||||
}
|
||||
EditorScript::DeleteField { field_meta } => {
|
||||
if self.editor.contain_field(&field_meta.id).await {
|
||||
self.field_count -= 1;
|
||||
}
|
||||
|
||||
self.editor.delete_field(&field_meta.id).await.unwrap();
|
||||
self.field_metas = self.editor.get_field_metas::<FieldOrder>(None).await.unwrap();
|
||||
assert_eq!(self.field_count, self.field_metas.len());
|
||||
}
|
||||
EditorScript::AssertFieldCount(count) => {
|
||||
assert_eq!(
|
||||
self.editor.get_field_metas::<FieldOrder>(None).await.unwrap().len(),
|
||||
count
|
||||
);
|
||||
}
|
||||
EditorScript::AssertFieldEqual {
|
||||
field_index,
|
||||
field_meta,
|
||||
} => {
|
||||
let field_metas = self.editor.get_field_metas::<FieldOrder>(None).await.unwrap();
|
||||
assert_eq!(field_metas[field_index].clone(), field_meta);
|
||||
}
|
||||
EditorScript::CreateBlock { block } => {
|
||||
self.editor.create_block(block).await.unwrap();
|
||||
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
|
||||
}
|
||||
EditorScript::UpdateBlock { changeset: change } => {
|
||||
self.editor.update_block(change).await.unwrap();
|
||||
}
|
||||
EditorScript::AssertBlockCount(count) => {
|
||||
assert_eq!(self.editor.get_block_metas().await.unwrap().len(), count);
|
||||
}
|
||||
EditorScript::AssertBlock {
|
||||
block_index,
|
||||
row_count,
|
||||
start_row_index,
|
||||
} => {
|
||||
assert_eq!(self.grid_blocks[block_index].row_count, row_count);
|
||||
assert_eq!(self.grid_blocks[block_index].start_row_index, start_row_index);
|
||||
}
|
||||
EditorScript::AssertBlockEqual { block_index, block } => {
|
||||
let blocks = self.editor.get_block_metas().await.unwrap();
|
||||
let compared_block = blocks[block_index].clone();
|
||||
assert_eq!(compared_block, block);
|
||||
}
|
||||
EditorScript::CreateEmptyRow => {
|
||||
let row_order = self.editor.create_row(None).await.unwrap();
|
||||
self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order);
|
||||
self.row_metas = self.get_row_metas().await;
|
||||
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
|
||||
}
|
||||
EditorScript::CreateRow { context } => {
|
||||
let row_orders = self.editor.insert_rows(vec![context]).await.unwrap();
|
||||
for row_order in row_orders {
|
||||
self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order);
|
||||
}
|
||||
self.row_metas = self.get_row_metas().await;
|
||||
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
|
||||
}
|
||||
EditorScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(),
|
||||
EditorScript::DeleteRow { row_ids } => {
|
||||
let row_orders = row_ids
|
||||
.into_iter()
|
||||
.map(|row_id| self.row_order_by_row_id.get(&row_id).unwrap().clone())
|
||||
.collect::<Vec<RowOrder>>();
|
||||
|
||||
self.editor.delete_rows(row_orders).await.unwrap();
|
||||
self.row_metas = self.get_row_metas().await;
|
||||
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
|
||||
}
|
||||
EditorScript::AssertRow { changeset } => {
|
||||
let row = self.row_metas.iter().find(|row| row.id == changeset.row_id).unwrap();
|
||||
|
||||
if let Some(visibility) = changeset.visibility {
|
||||
assert_eq!(row.visibility, visibility);
|
||||
}
|
||||
|
||||
if let Some(height) = changeset.height {
|
||||
assert_eq!(row.height, height);
|
||||
}
|
||||
}
|
||||
EditorScript::UpdateCell { changeset, is_err } => {
|
||||
let result = self.editor.update_cell(changeset).await;
|
||||
if is_err {
|
||||
assert!(result.is_err())
|
||||
} else {
|
||||
let _ = result.unwrap();
|
||||
self.row_metas = self.get_row_metas().await;
|
||||
}
|
||||
}
|
||||
EditorScript::AssertRowCount(count) => {
|
||||
assert_eq!(self.row_metas.len(), count);
|
||||
}
|
||||
EditorScript::AssertGridMetaPad => {
|
||||
sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await;
|
||||
let mut grid_rev_manager = grid_manager.make_grid_rev_manager(&self.grid_id, pool.clone()).unwrap();
|
||||
let grid_pad = grid_rev_manager.load::<GridPadBuilder>(None).await.unwrap();
|
||||
println!("{}", grid_pad.delta_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_row_metas(&self) -> Vec<Arc<RowMeta>> {
|
||||
get_row_metas(&self.editor).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_row_metas(editor: &Arc<GridMetaEditor>) -> Vec<Arc<RowMeta>> {
|
||||
editor
|
||||
.grid_block_snapshots(None)
|
||||
.await
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap()
|
||||
.row_metas
|
||||
}
|
||||
|
||||
pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) {
|
||||
let field_meta = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
let cloned_field_meta = field_meta.clone();
|
||||
|
||||
let type_option_data = field_meta
|
||||
.get_type_option_entry::<RichTextTypeOptionPB>(&field_meta.field_type)
|
||||
.unwrap()
|
||||
.protobuf_bytes()
|
||||
.to_vec();
|
||||
|
||||
let field = Field {
|
||||
id: field_meta.id,
|
||||
name: field_meta.name,
|
||||
desc: field_meta.desc,
|
||||
field_type: field_meta.field_type,
|
||||
frozen: field_meta.frozen,
|
||||
visibility: field_meta.visibility,
|
||||
width: field_meta.width,
|
||||
is_primary: false,
|
||||
};
|
||||
|
||||
let params = InsertFieldParams {
|
||||
grid_id: grid_id.to_owned(),
|
||||
field,
|
||||
type_option_data,
|
||||
start_field_id: None,
|
||||
};
|
||||
(params, cloned_field_meta)
|
||||
}
|
||||
|
||||
pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) {
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Done"))
|
||||
.option(SelectOption::new("Progress"));
|
||||
|
||||
let field_meta = FieldBuilder::new(single_select).name("Name").visibility(true).build();
|
||||
let cloned_field_meta = field_meta.clone();
|
||||
let type_option_data = field_meta
|
||||
.get_type_option_entry::<SingleSelectTypeOption>(&field_meta.field_type)
|
||||
.unwrap()
|
||||
.protobuf_bytes()
|
||||
.to_vec();
|
||||
|
||||
let field = Field {
|
||||
id: field_meta.id,
|
||||
name: field_meta.name,
|
||||
desc: field_meta.desc,
|
||||
field_type: field_meta.field_type,
|
||||
frozen: field_meta.frozen,
|
||||
visibility: field_meta.visibility,
|
||||
width: field_meta.width,
|
||||
is_primary: false,
|
||||
};
|
||||
|
||||
let params = InsertFieldParams {
|
||||
grid_id: grid_id.to_owned(),
|
||||
field,
|
||||
type_option_data,
|
||||
start_field_id: None,
|
||||
};
|
||||
(params, cloned_field_meta)
|
||||
}
|
||||
|
||||
fn make_template_1_grid() -> BuildGridContext {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
// Single Select
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Live"))
|
||||
.option(SelectOption::new("Completed"))
|
||||
.option(SelectOption::new("Planned"))
|
||||
.option(SelectOption::new("Paused"));
|
||||
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
|
||||
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Google"))
|
||||
.option(SelectOption::new("Facebook"))
|
||||
.option(SelectOption::new("Twitter"));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
// Number
|
||||
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
|
||||
|
||||
// Date
|
||||
let date = DateTypeOptionBuilder::default()
|
||||
.date_format(DateFormat::US)
|
||||
.time_format(TimeFormat::TwentyFourHour);
|
||||
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
|
||||
|
||||
// Checkbox
|
||||
let checkbox = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build();
|
||||
|
||||
// URL
|
||||
let url = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
|
||||
GridBuilder::default()
|
||||
.add_field(text_field)
|
||||
.add_field(single_select_field)
|
||||
.add_field(multi_select_field)
|
||||
.add_field(number_field)
|
||||
.add_field(date_field)
|
||||
.add_field(checkbox_field)
|
||||
.add_field(url_field)
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.build()
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
use crate::grid::database_editor::DatabaseEditorTest;
|
||||
|
||||
use flowy_client_sync::client_database::{DatabaseOperations, DatabaseRevisionPad};
|
||||
use flowy_revision::{RevisionSnapshot, REVISION_WRITE_INTERVAL_IN_MILLIS};
|
||||
@ -26,15 +26,15 @@ pub enum SnapshotScript {
|
||||
},
|
||||
}
|
||||
|
||||
pub struct GridSnapshotTest {
|
||||
inner: GridEditorTest,
|
||||
pub struct DatabaseSnapshotTest {
|
||||
inner: DatabaseEditorTest,
|
||||
pub current_snapshot: Option<RevisionSnapshot>,
|
||||
pub current_revision: Option<Revision>,
|
||||
}
|
||||
|
||||
impl GridSnapshotTest {
|
||||
impl DatabaseSnapshotTest {
|
||||
pub async fn new() -> Self {
|
||||
let editor_test = GridEditorTest::new_table().await;
|
||||
let editor_test = DatabaseEditorTest::new_table().await;
|
||||
Self {
|
||||
inner: editor_test,
|
||||
current_snapshot: None,
|
||||
@ -88,15 +88,15 @@ impl GridSnapshotTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::ops::Deref for GridSnapshotTest {
|
||||
type Target = GridEditorTest;
|
||||
impl std::ops::Deref for DatabaseSnapshotTest {
|
||||
type Target = DatabaseEditorTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for GridSnapshotTest {
|
||||
impl std::ops::DerefMut for DatabaseSnapshotTest {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::grid::field_test::util::create_text_field;
|
||||
use crate::grid::snapshot_test::script::{GridSnapshotTest, SnapshotScript::*};
|
||||
use crate::grid::snapshot_test::script::{DatabaseSnapshotTest, SnapshotScript::*};
|
||||
|
||||
#[tokio::test]
|
||||
async fn snapshot_create_test() {
|
||||
let mut test = GridSnapshotTest::new().await;
|
||||
let mut test = DatabaseSnapshotTest::new().await;
|
||||
let (_, field_rev) = create_text_field(&test.grid_id());
|
||||
let scripts = vec![CreateField { field_rev }, WriteSnapshot];
|
||||
test.run_scripts(scripts).await;
|
||||
@ -19,7 +19,7 @@ async fn snapshot_create_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn snapshot_multi_version_test() {
|
||||
let mut test = GridSnapshotTest::new().await;
|
||||
let mut test = DatabaseSnapshotTest::new().await;
|
||||
let original_content = test.grid_pad().await.json_str().unwrap();
|
||||
|
||||
// Create a field
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::grid::sort_test::script::{GridSortTest, SortScript::*};
|
||||
use crate::grid::sort_test::script::{DatabaseSortTest, SortScript::*};
|
||||
use flowy_database::entities::FieldType;
|
||||
use grid_model::SortCondition;
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_checkbox_and_then_text_by_descending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
|
||||
let text_field = test.get_first_field_rev(FieldType::RichText);
|
||||
let scripts = vec![
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::grid::sort_test::script::GridSortTest;
|
||||
use crate::grid::sort_test::script::DatabaseSortTest;
|
||||
use crate::grid::sort_test::script::SortScript::*;
|
||||
use flowy_database::entities::FieldType;
|
||||
use grid_model::SortCondition;
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_text_with_checkbox_by_ascending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
|
||||
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox).clone();
|
||||
let scripts = vec![
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
use crate::grid::database_editor::DatabaseEditorTest;
|
||||
use async_stream::stream;
|
||||
use flowy_database::entities::{AlterSortParams, CellPathParams, DeleteSortParams};
|
||||
use flowy_database::services::sort::SortType;
|
||||
@ -36,15 +36,15 @@ pub enum SortScript {
|
||||
},
|
||||
}
|
||||
|
||||
pub struct GridSortTest {
|
||||
inner: GridEditorTest,
|
||||
pub struct DatabaseSortTest {
|
||||
inner: DatabaseEditorTest,
|
||||
pub current_sort_rev: Option<SortRevision>,
|
||||
recv: Option<Receiver<GridViewChanged>>,
|
||||
}
|
||||
|
||||
impl GridSortTest {
|
||||
impl DatabaseSortTest {
|
||||
pub async fn new() -> Self {
|
||||
let editor_test = GridEditorTest::new_table().await;
|
||||
let editor_test = DatabaseEditorTest::new_table().await;
|
||||
Self {
|
||||
inner: editor_test,
|
||||
current_sort_rev: None,
|
||||
@ -154,15 +154,15 @@ async fn assert_sort_changed(
|
||||
.await;
|
||||
}
|
||||
|
||||
impl std::ops::Deref for GridSortTest {
|
||||
type Target = GridEditorTest;
|
||||
impl std::ops::Deref for DatabaseSortTest {
|
||||
type Target = DatabaseEditorTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for GridSortTest {
|
||||
impl std::ops::DerefMut for DatabaseSortTest {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::grid::sort_test::script::{GridSortTest, SortScript::*};
|
||||
use crate::grid::sort_test::script::{DatabaseSortTest, SortScript::*};
|
||||
use flowy_database::entities::FieldType;
|
||||
use grid_model::SortCondition;
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_text_by_ascending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let text_field = test.get_first_field_rev(FieldType::RichText);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
@ -25,7 +25,7 @@ async fn sort_text_by_ascending_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_change_notification_by_update_text_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
|
||||
let scripts = vec![
|
||||
InsertSort {
|
||||
@ -57,7 +57,7 @@ async fn sort_change_notification_by_update_text_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_text_by_ascending_and_delete_sort_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
|
||||
let scripts = vec![InsertSort {
|
||||
field_rev: text_field.clone(),
|
||||
@ -80,7 +80,7 @@ async fn sort_text_by_ascending_and_delete_sort_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_text_by_descending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let text_field = test.get_first_field_rev(FieldType::RichText);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
@ -101,7 +101,7 @@ async fn sort_text_by_descending_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_checkbox_by_ascending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
@ -118,7 +118,7 @@ async fn sort_checkbox_by_ascending_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_checkbox_by_descending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
@ -139,7 +139,7 @@ async fn sort_checkbox_by_descending_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_date_by_ascending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let date_field = test.get_first_field_rev(FieldType::DateTime);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
@ -160,7 +160,7 @@ async fn sort_date_by_ascending_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_date_by_descending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let date_field = test.get_first_field_rev(FieldType::DateTime);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
@ -195,7 +195,7 @@ async fn sort_date_by_descending_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_number_by_descending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let number_field = test.get_first_field_rev(FieldType::Number);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
@ -216,7 +216,7 @@ async fn sort_number_by_descending_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_single_select_by_descending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let single_select = test.get_first_field_rev(FieldType::SingleSelect);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
@ -237,7 +237,7 @@ async fn sort_single_select_by_descending_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_multi_select_by_ascending_test() {
|
||||
let mut test = GridSortTest::new().await;
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let multi_select = test.get_first_field_rev(FieldType::MultiSelect);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
|
@ -5,8 +5,6 @@ use std::convert::TryInto;
|
||||
impl lib_dispatch::Error for FlowyError {
|
||||
fn as_response(&self) -> AFPluginEventResponse {
|
||||
let bytes: Bytes = self.clone().try_into().unwrap();
|
||||
|
||||
println!("Serialize FlowyError: {:?} to event response", self);
|
||||
ResponseBuilder::Err().data(bytes).build()
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ pub mod helper;
|
||||
|
||||
use crate::helper::*;
|
||||
|
||||
use flowy_core::{FlowySDK, FlowySDKConfig};
|
||||
use flowy_core::{AppFlowyCore, AppFlowyCoreConfig};
|
||||
use flowy_document::entities::DocumentVersionPB;
|
||||
use flowy_net::get_client_server_configuration;
|
||||
use flowy_user::entities::UserProfilePB;
|
||||
@ -16,11 +16,11 @@ pub mod prelude {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FlowySDKTest {
|
||||
pub inner: FlowySDK,
|
||||
pub inner: AppFlowyCore,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for FlowySDKTest {
|
||||
type Target = FlowySDK;
|
||||
type Target = AppFlowyCore;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
@ -36,10 +36,10 @@ impl std::default::Default for FlowySDKTest {
|
||||
impl FlowySDKTest {
|
||||
pub fn new(document_version: DocumentVersionPB) -> Self {
|
||||
let server_config = get_client_server_configuration().unwrap();
|
||||
let config = FlowySDKConfig::new(&root_dir(), nanoid!(6), server_config)
|
||||
let config = AppFlowyCoreConfig::new(&root_dir(), nanoid!(6), server_config)
|
||||
.with_document_version(document_version)
|
||||
.log_filter("info", vec![]);
|
||||
let sdk = std::thread::spawn(|| FlowySDK::new(config)).join().unwrap();
|
||||
let sdk = std::thread::spawn(|| AppFlowyCore::new(config)).join().unwrap();
|
||||
std::mem::forget(sdk.dispatcher());
|
||||
Self { inner: sdk }
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
echo "Start building rust sdk"
|
||||
rustup show
|
||||
cargo make --profile development-windows-x86 appflowy-sdk-dev
|
||||
cargo make --profile development-windows-x86 appflowy-core-dev
|
@ -17,15 +17,15 @@ rustup show
|
||||
|
||||
case "$FLOWY_DEV_ENV" in
|
||||
Linux)
|
||||
cargo make --profile "development-linux-$(uname -m)" appflowy-sdk-dev
|
||||
cargo make --profile "development-linux-$(uname -m)" appflowy-core-dev
|
||||
;;
|
||||
|
||||
macOS)
|
||||
cargo make --profile "development-mac-$(uname -m)" appflowy-sdk-dev
|
||||
cargo make --profile "development-mac-$(uname -m)" appflowy-core-dev
|
||||
;;
|
||||
|
||||
Windows)
|
||||
cargo make --profile development-windows appflowy-sdk-dev
|
||||
cargo make --profile development-windows appflowy-core-dev
|
||||
;;
|
||||
|
||||
*)
|
||||
|
@ -1,8 +1,3 @@
|
||||
# cargo make --profile production task
|
||||
|
||||
# Run the task with profile, e.g.
|
||||
# cargo make --profile development-mac appflowy-sdk-dev
|
||||
# cargo make --profile production-windows-x86 appflowy-sdk-dev
|
||||
|
||||
[tasks.env_check]
|
||||
dependencies = ["echo_env", "install_flutter_protobuf"]
|
||||
@ -15,12 +10,12 @@ condition = { env_set = [
|
||||
"stable",
|
||||
] }
|
||||
|
||||
[tasks.appflowy-sdk-dev]
|
||||
mac_alias = "appflowy-sdk-dev-macos"
|
||||
windows_alias = "appflowy-sdk-dev-windows"
|
||||
linux_alias = "appflowy-sdk-dev-linux"
|
||||
[tasks.appflowy-core-dev]
|
||||
mac_alias = "appflowy-core-dev-macos"
|
||||
windows_alias = "appflowy-core-dev-windows"
|
||||
linux_alias = "appflowy-core-dev-linux"
|
||||
|
||||
[tasks.appflowy-sdk-dev-android]
|
||||
[tasks.appflowy-core-dev-android]
|
||||
category = "Build"
|
||||
dependencies = ["env_check"]
|
||||
run_task = { name = [
|
||||
@ -29,7 +24,7 @@ run_task = { name = [
|
||||
"restore-crate-type",
|
||||
] }
|
||||
|
||||
[tasks.appflowy-sdk-dev-macos]
|
||||
[tasks.appflowy-core-dev-macos]
|
||||
category = "Build"
|
||||
dependencies = ["env_check"]
|
||||
run_task = { name = [
|
||||
@ -39,7 +34,7 @@ run_task = { name = [
|
||||
"restore-crate-type",
|
||||
] }
|
||||
|
||||
[tasks.appflowy-sdk-dev-windows]
|
||||
[tasks.appflowy-core-dev-windows]
|
||||
category = "Build"
|
||||
dependencies = ["env_check"]
|
||||
run_task = { name = [
|
||||
@ -49,7 +44,7 @@ run_task = { name = [
|
||||
"restore-crate-type",
|
||||
] }
|
||||
|
||||
[tasks.appflowy-sdk-dev-linux]
|
||||
[tasks.appflowy-core-dev-linux]
|
||||
category = "Build"
|
||||
dependencies = ["env_check"]
|
||||
run_task = { name = [
|
||||
@ -59,7 +54,6 @@ run_task = { name = [
|
||||
"restore-crate-type",
|
||||
] }
|
||||
|
||||
|
||||
#
|
||||
[tasks.sdk-build]
|
||||
private = true
|
||||
@ -112,7 +106,7 @@ script = [
|
||||
script_runner = "@duckscript"
|
||||
|
||||
#
|
||||
[tasks.appflowy-sdk-release]
|
||||
[tasks.appflowy-core-release]
|
||||
description = "Build flowy sdk in release mode"
|
||||
category = "Build"
|
||||
dependencies = ["env_check"]
|
||||
@ -144,7 +138,7 @@ linux_alias = "post-desktop-linux"
|
||||
private = true
|
||||
script = [
|
||||
"""
|
||||
echo "🚀 🚀 🚀 Flowy-SDK(macOS) build success"
|
||||
echo "🚀 🚀 🚀 AppFlowy-Core build success"
|
||||
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/packages/appflowy_backend/${TARGET_OS}
|
||||
lib = set lib${LIB_NAME}.${LIB_EXT}
|
||||
|
||||
@ -161,7 +155,7 @@ script_runner = "@duckscript"
|
||||
private = true
|
||||
script = [
|
||||
"""
|
||||
echo "🚀 🚀 🚀 Flowy-SDK(windows) build success"
|
||||
echo "🚀 🚀 🚀 AppFlowy-Core build success"
|
||||
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/windows/flutter/dart_ffi
|
||||
lib = set ${LIB_NAME}.${LIB_EXT}
|
||||
|
||||
@ -180,7 +174,7 @@ script_runner = "@duckscript"
|
||||
private = true
|
||||
script = [
|
||||
"""
|
||||
echo "🚀 🚀 🚀 Flowy-SDK(linux) build success"
|
||||
echo "🚀 🚀 🚀 AppFlowy-Core build success"
|
||||
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/linux/flutter/dart_ffi
|
||||
lib = set lib${LIB_NAME}.${LIB_EXT}
|
||||
|
||||
|
@ -4,17 +4,33 @@ windows_alias = "appflowy-windows"
|
||||
linux_alias = "appflowy-linux"
|
||||
|
||||
[tasks.appflowy-macos]
|
||||
dependencies = ["appflowy-sdk-release"]
|
||||
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
|
||||
dependencies = ["appflowy-core-release"]
|
||||
run_task = { name = [
|
||||
"code_generation",
|
||||
"set-app-version",
|
||||
"flutter-build",
|
||||
"copy-to-product",
|
||||
] }
|
||||
script_runner = "@shell"
|
||||
|
||||
[tasks.appflowy-windows]
|
||||
dependencies = ["appflowy-sdk-release"]
|
||||
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
|
||||
dependencies = ["appflowy-core-release"]
|
||||
run_task = { name = [
|
||||
"code_generation",
|
||||
"set-app-version",
|
||||
"flutter-build",
|
||||
"copy-to-product",
|
||||
] }
|
||||
|
||||
[tasks.appflowy-linux]
|
||||
dependencies = ["appflowy-sdk-release"]
|
||||
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product", "create-release-archive"] }
|
||||
dependencies = ["appflowy-core-release"]
|
||||
run_task = { name = [
|
||||
"code_generation",
|
||||
"set-app-version",
|
||||
"flutter-build",
|
||||
"copy-to-product",
|
||||
"create-release-archive",
|
||||
] }
|
||||
script_runner = "@shell"
|
||||
|
||||
[tasks.appflowy-dev]
|
||||
@ -23,17 +39,32 @@ windows_alias = "appflowy-windows-dev"
|
||||
linux_alias = "appflowy-linux-dev"
|
||||
|
||||
[tasks.appflowy-macos-dev]
|
||||
dependencies = ["appflowy-sdk-dev"]
|
||||
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
|
||||
dependencies = ["appflowy-core-dev"]
|
||||
run_task = { name = [
|
||||
"code_generation",
|
||||
"set-app-version",
|
||||
"flutter-build",
|
||||
"copy-to-product",
|
||||
] }
|
||||
script_runner = "@shell"
|
||||
|
||||
[tasks.appflowy-windows-dev]
|
||||
dependencies = ["appflowy-sdk-dev"]
|
||||
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
|
||||
dependencies = ["appflowy-core-dev"]
|
||||
run_task = { name = [
|
||||
"code_generation",
|
||||
"set-app-version",
|
||||
"flutter-build",
|
||||
"copy-to-product",
|
||||
] }
|
||||
|
||||
[tasks.appflowy-linux-dev]
|
||||
dependencies = ["appflowy-sdk-dev"]
|
||||
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
|
||||
dependencies = ["appflowy-core-dev"]
|
||||
run_task = { name = [
|
||||
"code_generation",
|
||||
"set-app-version",
|
||||
"flutter-build",
|
||||
"copy-to-product",
|
||||
] }
|
||||
script_runner = "@shell"
|
||||
|
||||
[tasks.copy-to-product]
|
||||
@ -96,15 +127,13 @@ script = [
|
||||
script_runner = "@duckscript"
|
||||
|
||||
[tasks.set-app-version]
|
||||
script = [
|
||||
"""
|
||||
script = ["""
|
||||
if is_empty ${APP_VERSION}
|
||||
APP_VERSION = set ${CURRENT_APP_VERSION}
|
||||
set_env APP_VERSION ${CURRENT_APP_VERSION}
|
||||
end
|
||||
echo APP_VERSION: ${APP_VERSION}
|
||||
""",
|
||||
]
|
||||
"""]
|
||||
script_runner = "@duckscript"
|
||||
|
||||
# The following tasks will create an archive that will be used on the GitHub Releases section
|
||||
@ -118,7 +147,7 @@ linux_alias = "create-release-archive-linux"
|
||||
[tasks.create-release-archive-linux]
|
||||
script = [
|
||||
"cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/product/${APP_VERSION}/${TARGET_OS}/Release",
|
||||
"tar -czf ${PRODUCT_NAME}-${TARGET_OS}-x86.tar.gz *"
|
||||
"tar -czf ${PRODUCT_NAME}-${TARGET_OS}-x86.tar.gz *",
|
||||
]
|
||||
|
||||
[tasks.create-release-archive-windows]
|
||||
@ -136,36 +165,30 @@ script = [
|
||||
]
|
||||
|
||||
[tasks.flutter-build]
|
||||
script = [
|
||||
"""
|
||||
script = ["""
|
||||
cd app_flowy/
|
||||
flutter clean
|
||||
flutter pub get
|
||||
flutter build ${TARGET_OS} --${BUILD_FLAG} --build-name=${APP_VERSION}
|
||||
""",
|
||||
]
|
||||
"""]
|
||||
script_runner = "@shell"
|
||||
|
||||
[tasks.flutter-build.linux]
|
||||
script = [
|
||||
"""
|
||||
script = ["""
|
||||
cd app_flowy/
|
||||
flutter clean
|
||||
flutter pub get
|
||||
flutter build ${TARGET_OS} --${BUILD_FLAG}
|
||||
""",
|
||||
]
|
||||
"""]
|
||||
script_runner = "@shell"
|
||||
|
||||
[tasks.flutter-build.windows]
|
||||
script = [
|
||||
"""
|
||||
script = ["""
|
||||
cd app_flowy
|
||||
exec cmd.exe /c flutter clean
|
||||
exec cmd.exe /c flutter pub get
|
||||
exec cmd.exe /c flutter build ${TARGET_OS} --${BUILD_FLAG}
|
||||
""",
|
||||
]
|
||||
"""]
|
||||
script_runner = "@duckscript"
|
||||
|
||||
[tasks.code_generation]
|
||||
@ -177,7 +200,7 @@ script = [
|
||||
flutter packages pub get
|
||||
flutter packages pub run easy_localization:generate -S assets/translations/ -f keys -o locale_keys.g.dart -S assets/translations -s en.json
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
"""
|
||||
""",
|
||||
]
|
||||
|
||||
[tasks.code_generation.windows]
|
||||
@ -189,7 +212,7 @@ script = [
|
||||
exec cmd.exe /c flutter packages pub get
|
||||
exec cmd.exe /c flutter packages pub run easy_localization:generate -S assets/translations/ -f keys -o locale_keys.g.dart -S assets/translations -s en.json
|
||||
exec cmd.exe /c flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
"""
|
||||
""",
|
||||
]
|
||||
|
||||
[tasks.dry_code_generation]
|
||||
@ -199,7 +222,7 @@ script = [
|
||||
cd app_flowy
|
||||
flutter packages pub run easy_localization:generate -S assets/translations/ -f keys -o locale_keys.g.dart -S assets/translations -s en.json
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
"""
|
||||
""",
|
||||
]
|
||||
|
||||
[tasks.dry_code_generation.windows]
|
||||
@ -209,5 +232,5 @@ script = [
|
||||
cd ./app_flowy/
|
||||
exec cmd.exe /c flutter packages pub run easy_localization:generate -S assets/translations/ -f keys -o locale_keys.g.dart -S assets/translations -s en.json
|
||||
exec cmd.exe /c flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
"""
|
||||
""",
|
||||
]
|
||||
|
@ -7,6 +7,7 @@ script = ["""
|
||||
script_runner = "@shell"
|
||||
|
||||
[tasks.tauri_dev]
|
||||
env = { RUST_LOG = "debug" }
|
||||
script = ["""
|
||||
cd appflowy_tauri
|
||||
npm run tauri dev
|
||||
|
@ -18,6 +18,7 @@ cargo make --profile test-linux dart_unit_test_inner
|
||||
script_runner = "@shell"
|
||||
|
||||
[tasks.dart_unit_test_inner]
|
||||
env = { RUST_LOG = "info" }
|
||||
dependencies = ["build-test-lib"]
|
||||
description = "Run flutter unit tests"
|
||||
script = '''
|
||||
@ -29,6 +30,7 @@ flutter test --dart-define=RUST_LOG=${TEST_RUST_LOG} --concurrency=1
|
||||
run_task = { name = ["rust_lib_unit_test", "shared_lib_unit_test"] }
|
||||
|
||||
[tasks.rust_lib_unit_test]
|
||||
env = { RUST_LOG = "info" }
|
||||
description = "Run rust-lib unit tests"
|
||||
script = '''
|
||||
cd rust-lib
|
||||
@ -36,6 +38,7 @@ cargo test --no-default-features --features="sync, rev-sqlite"
|
||||
'''
|
||||
|
||||
[tasks.shared_lib_unit_test]
|
||||
env = { RUST_LOG = "info" }
|
||||
description = "Run shared-lib unit test"
|
||||
script = '''
|
||||
cd ../shared-lib
|
||||
@ -62,8 +65,7 @@ fi
|
||||
[tasks.clean_profraw_files]
|
||||
description = "Cleans profraw files that are created by `cargo test`"
|
||||
script_runner = "@duckscript"
|
||||
script = [
|
||||
"""
|
||||
script = ["""
|
||||
rust_lib_profs = glob_array ./rust-lib/**/*.profraw
|
||||
for prof in ${rust_lib_profs}
|
||||
full_path = canonicalize ${prof}
|
||||
@ -76,14 +78,12 @@ script = [
|
||||
rm ${full_path}
|
||||
end
|
||||
|
||||
"""
|
||||
]
|
||||
"""]
|
||||
|
||||
[tasks.run_rustlib_coverage_tests]
|
||||
description = "Run tests with coverage instrumentation"
|
||||
script_runner = "@shell"
|
||||
script = [
|
||||
"""
|
||||
script = ["""
|
||||
echo --- Running coverage tests ---
|
||||
cd rust-lib/
|
||||
|
||||
@ -91,14 +91,12 @@ script = [
|
||||
RUSTFLAGS='-C instrument-coverage' \
|
||||
LLVM_PROFILE_FILE='prof-%p-%m.profraw' \
|
||||
cargo test --no-default-features --features="sync,rev-sqlite"
|
||||
"""
|
||||
]
|
||||
"""]
|
||||
|
||||
[tasks.run_sharedlib_coverage_tests]
|
||||
description = "Run tests with coverage instrumentation"
|
||||
script_runner = "@shell"
|
||||
script = [
|
||||
"""
|
||||
script = ["""
|
||||
echo --- Running coverage tests ---
|
||||
cd ../shared-lib
|
||||
|
||||
@ -107,8 +105,7 @@ script = [
|
||||
LLVM_PROFILE_FILE='prof-%p-%m.profraw' \
|
||||
cargo test --no-default-features
|
||||
|
||||
"""
|
||||
]
|
||||
"""]
|
||||
|
||||
[tasks.get_rustlib_grcov_report]
|
||||
description = "Get `grcov` HTML report for test coverage for rust-lib"
|
||||
@ -128,7 +125,7 @@ script = [
|
||||
--output-path target/coverage-html
|
||||
|
||||
echo "--- Done! Generated HTML report under 'target/coverage-html' for rustlib."
|
||||
"""
|
||||
""",
|
||||
]
|
||||
|
||||
[tasks.get_sharedlib_grcov_report]
|
||||
@ -149,21 +146,20 @@ script = [
|
||||
--output-path target/coverage-html
|
||||
|
||||
echo "--- Done! Generated HTML report under 'target/coverage-html' for sharedlib."
|
||||
"""
|
||||
""",
|
||||
]
|
||||
|
||||
[tasks.get_grcov_report]
|
||||
description = "Get `grcov` HTML report for test coverage"
|
||||
run_task = { name = [
|
||||
"get_rustlib_grcov_report",
|
||||
"get_sharedlib_grcov_report"
|
||||
"get_sharedlib_grcov_report",
|
||||
], parallel = true }
|
||||
|
||||
[tasks.get_sharedlib_lcov_report]
|
||||
description = "Generates `lcov` report for `shared-lib`"
|
||||
script_runner = "@shell"
|
||||
script = [
|
||||
"""
|
||||
script = ["""
|
||||
echo Getting 'lcov' results for 'shared-lib'
|
||||
|
||||
cd ../shared-lib
|
||||
@ -178,14 +174,12 @@ script = [
|
||||
--output-path target/coverage.lcov
|
||||
|
||||
echo "--- Done! Generated 'target/coverage.lcov' sharedlib."
|
||||
"""
|
||||
]
|
||||
"""]
|
||||
|
||||
[tasks.get_rustlib_lcov_report]
|
||||
description = "Generates `lcov` report for `rust-lib`"
|
||||
script_runner = "@shell"
|
||||
script = [
|
||||
"""
|
||||
script = ["""
|
||||
echo Getting 'lcov' results for 'rust-lib'
|
||||
|
||||
cd rust-lib/
|
||||
@ -200,23 +194,22 @@ script = [
|
||||
--output-path target/coverage.lcov
|
||||
|
||||
echo "--- Done! Generated 'target/coverage.lcov' for rustlib."
|
||||
"""
|
||||
]
|
||||
"""]
|
||||
|
||||
[tasks.get_lcov_report]
|
||||
description = "Get `lcov` reports for test coverage"
|
||||
run_task = { name = [
|
||||
"get_sharedlib_lcov_report",
|
||||
"get_rustlib_lcov_report"
|
||||
"get_rustlib_lcov_report",
|
||||
], parallel = true }
|
||||
|
||||
[tasks.rust_unit_test_with_coverage]
|
||||
description = "Run rust unit test with code coverage"
|
||||
run_task = { name = [
|
||||
"check_grcov",
|
||||
'appflowy-flutter-deps-tools',
|
||||
"run_rustlib_coverage_tests",
|
||||
"run_sharedlib_coverage_tests",
|
||||
"get_lcov_report",
|
||||
"clean_profraw_files"
|
||||
]}
|
||||
"check_grcov",
|
||||
'appflowy-flutter-deps-tools',
|
||||
"run_rustlib_coverage_tests",
|
||||
"run_sharedlib_coverage_tests",
|
||||
"get_lcov_report",
|
||||
"clean_profraw_files",
|
||||
] }
|
||||
|
@ -10,7 +10,7 @@ chrono = "0.4.19"
|
||||
bytes = { version = "1.0" }
|
||||
pin-project = "1.0.12"
|
||||
futures-core = { version = "0.3" }
|
||||
tokio = { version = "1.0", features = ["time", "rt"] }
|
||||
tokio = { version = "1", features = ["time", "rt"] }
|
||||
rand = "0.8.5"
|
||||
async-trait = "0.1.59"
|
||||
md5 = "0.7.0"
|
||||
|
Loading…
Reference in New Issue
Block a user