mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
1. rename flowy_str functions
2. mv the document_test to flowy_document crate
This commit is contained in:
1
backend/Cargo.lock
generated
1
backend/Cargo.lock
generated
@ -1436,7 +1436,6 @@ dependencies = [
|
|||||||
"claim",
|
"claim",
|
||||||
"flowy-collaboration",
|
"flowy-collaboration",
|
||||||
"flowy-core",
|
"flowy-core",
|
||||||
"flowy-document",
|
|
||||||
"flowy-net",
|
"flowy-net",
|
||||||
"flowy-sdk",
|
"flowy-sdk",
|
||||||
"flowy-user",
|
"flowy-user",
|
||||||
|
@ -37,7 +37,7 @@ pub enum DocScript {
|
|||||||
impl DocumentTest {
|
impl DocumentTest {
|
||||||
pub async fn new() -> Self {
|
pub async fn new() -> Self {
|
||||||
let server = spawn_server().await;
|
let server = spawn_server().await;
|
||||||
let flowy_test = FlowySDKTest::new(server.client_server_config.clone(), None);
|
let flowy_test = FlowySDKTest::new(server.client_server_config.clone());
|
||||||
Self { server, flowy_test }
|
Self { server, flowy_test }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ impl DocumentRevisionCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RevisionMemoryCacheDelegate for Arc<SQLitePersistence> {
|
impl RevisionMemoryCacheDelegate for Arc<SQLitePersistence> {
|
||||||
#[tracing::instrument(level = "debug", skip(self, records), fields(checkpoint_result), err)]
|
#[tracing::instrument(level = "trace", skip(self, records), fields(checkpoint_result), err)]
|
||||||
fn checkpoint_tick(&self, mut records: Vec<RevisionRecord>) -> FlowyResult<()> {
|
fn checkpoint_tick(&self, mut records: Vec<RevisionRecord>) -> FlowyResult<()> {
|
||||||
let conn = &*self.pool.get().map_err(internal_error)?;
|
let conn = &*self.pool.get().map_err(internal_error)?;
|
||||||
records.retain(|record| record.write_to_disk);
|
records.retain(|record| record.write_to_disk);
|
||||||
|
@ -120,7 +120,7 @@ impl RevisionTableSql {
|
|||||||
.filter(dsl::rev_id.eq(changeset.rev_id.as_ref()))
|
.filter(dsl::rev_id.eq(changeset.rev_id.as_ref()))
|
||||||
.filter(dsl::doc_id.eq(changeset.doc_id));
|
.filter(dsl::doc_id.eq(changeset.doc_id));
|
||||||
let _ = update(filter).set(dsl::state.eq(changeset.state)).execute(conn)?;
|
let _ = update(filter).set(dsl::state.eq(changeset.state)).execute(conn)?;
|
||||||
tracing::debug!("Set {} to {:?}", changeset.rev_id, changeset.state);
|
tracing::debug!("Save revision:{} state to {:?}", changeset.rev_id, changeset.state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,11 +169,10 @@ impl RevisionSyncSequence {
|
|||||||
"The ack rev_id:{} is not equal to the current rev_id:{}",
|
"The ack rev_id:{} is not equal to the current rev_id:{}",
|
||||||
rev_id, pop_rev_id
|
rev_id, pop_rev_id
|
||||||
);
|
);
|
||||||
// tracing::error!("{}", desc);
|
|
||||||
return Err(FlowyError::internal().context(desc));
|
return Err(FlowyError::internal().context(desc));
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("pop revision {}", pop_rev_id);
|
tracing::trace!("{} revision finish synchronizing", pop_rev_id);
|
||||||
self.revs_map.remove(&pop_rev_id);
|
self.revs_map.remove(&pop_rev_id);
|
||||||
let _ = self.local_revs.write().await.pop_front();
|
let _ = self.local_revs.write().await.pop_front();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
use crate::document::edit_script::{EditorScript::*, *};
|
||||||
|
use flowy_collaboration::entities::revision::RevisionState;
|
||||||
|
use lib_ot::core::{count_utf16_code_units, Interval};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn document_sync_current_rev_id_check() {
|
||||||
|
let scripts = vec![
|
||||||
|
InsertText("1", 0),
|
||||||
|
AssertCurrentRevId(1),
|
||||||
|
InsertText("2", 1),
|
||||||
|
AssertCurrentRevId(2),
|
||||||
|
InsertText("3", 2),
|
||||||
|
AssertCurrentRevId(3),
|
||||||
|
AssertNextRevId(None),
|
||||||
|
AssertJson(r#"[{"insert":"123\n"}]"#),
|
||||||
|
];
|
||||||
|
EditorTest::new().await.run_scripts(scripts).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn document_sync_state_check() {
|
||||||
|
let scripts = vec![
|
||||||
|
InsertText("1", 0),
|
||||||
|
InsertText("2", 1),
|
||||||
|
InsertText("3", 2),
|
||||||
|
AssertRevisionState(1, RevisionState::Ack),
|
||||||
|
AssertRevisionState(2, RevisionState::Ack),
|
||||||
|
AssertRevisionState(3, RevisionState::Ack),
|
||||||
|
AssertJson(r#"[{"insert":"123\n"}]"#),
|
||||||
|
];
|
||||||
|
EditorTest::new().await.run_scripts(scripts).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn document_sync_insert_test() {
|
||||||
|
let scripts = vec![
|
||||||
|
InsertText("1", 0),
|
||||||
|
InsertText("2", 1),
|
||||||
|
InsertText("3", 2),
|
||||||
|
AssertJson(r#"[{"insert":"123\n"}]"#),
|
||||||
|
AssertNextRevId(None),
|
||||||
|
];
|
||||||
|
EditorTest::new().await.run_scripts(scripts).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn document_sync_delete_test1() {
|
||||||
|
let scripts = vec![
|
||||||
|
InsertText("1", 0),
|
||||||
|
InsertText("2", 1),
|
||||||
|
InsertText("3", 2),
|
||||||
|
Delete(Interval::new(0, 2)),
|
||||||
|
AssertJson(r#"[{"insert":"3\n"}]"#),
|
||||||
|
];
|
||||||
|
EditorTest::new().await.run_scripts(scripts).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn document_sync_replace_test() {
|
||||||
|
let scripts = vec![
|
||||||
|
InsertText("1", 0),
|
||||||
|
InsertText("2", 1),
|
||||||
|
InsertText("3", 2),
|
||||||
|
Replace(Interval::new(0, 3), "abc"),
|
||||||
|
AssertJson(r#"[{"insert":"abc\n"}]"#),
|
||||||
|
];
|
||||||
|
EditorTest::new().await.run_scripts(scripts).await;
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
use crate::{helper::ViewTest, FlowySDKTest};
|
|
||||||
use backend_service::configuration::get_client_server_configuration;
|
|
||||||
use flowy_collaboration::entities::revision::RevisionState;
|
use flowy_collaboration::entities::revision::RevisionState;
|
||||||
use flowy_document::core::{edit::ClientDocumentEditor, SYNC_INTERVAL_IN_MILLIS};
|
use flowy_document::core::{edit::ClientDocumentEditor, SYNC_INTERVAL_IN_MILLIS};
|
||||||
|
use flowy_test::{helper::ViewTest, FlowySDKTest};
|
||||||
use lib_ot::{core::Interval, rich_text::RichTextDelta};
|
use lib_ot::{core::Interval, rich_text::RichTextDelta};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
@ -24,8 +23,7 @@ pub struct EditorTest {
|
|||||||
|
|
||||||
impl EditorTest {
|
impl EditorTest {
|
||||||
pub async fn new() -> Self {
|
pub async fn new() -> Self {
|
||||||
let server_config = get_client_server_configuration().unwrap();
|
let sdk = FlowySDKTest::default();
|
||||||
let sdk = FlowySDKTest::new(server_config, None);
|
|
||||||
let _ = sdk.init_user().await;
|
let _ = sdk.init_user().await;
|
||||||
let test = ViewTest::new(&sdk).await;
|
let test = ViewTest::new(&sdk).await;
|
||||||
let editor = sdk.document_ctx.controller.open_document(&test.view.id).await.unwrap();
|
let editor = sdk.document_ctx.controller.open_document(&test.view.id).await.unwrap();
|
||||||
@ -36,8 +34,6 @@ impl EditorTest {
|
|||||||
for script in scripts {
|
for script in scripts {
|
||||||
self.run_script(script).await;
|
self.run_script(script).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(Duration::from_secs(3)).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_script(&mut self, script: EditorScript) {
|
async fn run_script(&mut self, script: EditorScript) {
|
||||||
@ -46,7 +42,6 @@ impl EditorTest {
|
|||||||
let _user_id = self.sdk.user_session.user_id().unwrap();
|
let _user_id = self.sdk.user_session.user_id().unwrap();
|
||||||
// let ws_manager = self.sdk.ws_conn.clone();
|
// let ws_manager = self.sdk.ws_conn.clone();
|
||||||
// let token = self.sdk.user_session.token().unwrap();
|
// let token = self.sdk.user_session.token().unwrap();
|
||||||
let wait_millis = 2 * SYNC_INTERVAL_IN_MILLIS;
|
|
||||||
|
|
||||||
match script {
|
match script {
|
||||||
EditorScript::InsertText(s, offset) => {
|
EditorScript::InsertText(s, offset) => {
|
||||||
@ -84,6 +79,6 @@ impl EditorTest {
|
|||||||
assert_eq!(expected_delta, delta);
|
assert_eq!(expected_delta, delta);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
sleep(Duration::from_millis(wait_millis)).await;
|
sleep(Duration::from_millis(SYNC_INTERVAL_IN_MILLIS)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
2
frontend/rust-lib/flowy-document/tests/document/mod.rs
Normal file
2
frontend/rust-lib/flowy-document/tests/document/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mod document_test;
|
||||||
|
mod edit_script;
|
@ -724,7 +724,7 @@ fn attributes_preserve_header_format_on_merge() {
|
|||||||
fn attributes_format_emoji() {
|
fn attributes_format_emoji() {
|
||||||
let emoji_s = "👋 ";
|
let emoji_s = "👋 ";
|
||||||
let s: FlowyStr = emoji_s.into();
|
let s: FlowyStr = emoji_s.into();
|
||||||
let len = s.count_utf16_code_units();
|
let len = s.utf16_size();
|
||||||
assert_eq!(3, len);
|
assert_eq!(3, len);
|
||||||
assert_eq!(2, s.graphemes(true).count());
|
assert_eq!(2, s.graphemes(true).count());
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
|
@ -297,8 +297,9 @@ impl Rng {
|
|||||||
|
|
||||||
pub fn gen_delta(&mut self, s: &str) -> RichTextDelta {
|
pub fn gen_delta(&mut self, s: &str) -> RichTextDelta {
|
||||||
let mut delta = RichTextDelta::default();
|
let mut delta = RichTextDelta::default();
|
||||||
|
let s = FlowyStr::from(s);
|
||||||
loop {
|
loop {
|
||||||
let left = s.chars().count() - delta.base_len;
|
let left = s.utf16_size() - delta.utf16_base_len;
|
||||||
if left == 0 {
|
if left == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -298,20 +298,20 @@ fn delta_next_op_with_len_cross_op_return_last() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn lengths() {
|
fn lengths() {
|
||||||
let mut delta = RichTextDelta::default();
|
let mut delta = RichTextDelta::default();
|
||||||
assert_eq!(delta.base_len, 0);
|
assert_eq!(delta.utf16_base_len, 0);
|
||||||
assert_eq!(delta.target_len, 0);
|
assert_eq!(delta.utf16_target_len, 0);
|
||||||
delta.retain(5, RichTextAttributes::default());
|
delta.retain(5, RichTextAttributes::default());
|
||||||
assert_eq!(delta.base_len, 5);
|
assert_eq!(delta.utf16_base_len, 5);
|
||||||
assert_eq!(delta.target_len, 5);
|
assert_eq!(delta.utf16_target_len, 5);
|
||||||
delta.insert("abc", RichTextAttributes::default());
|
delta.insert("abc", RichTextAttributes::default());
|
||||||
assert_eq!(delta.base_len, 5);
|
assert_eq!(delta.utf16_base_len, 5);
|
||||||
assert_eq!(delta.target_len, 8);
|
assert_eq!(delta.utf16_target_len, 8);
|
||||||
delta.retain(2, RichTextAttributes::default());
|
delta.retain(2, RichTextAttributes::default());
|
||||||
assert_eq!(delta.base_len, 7);
|
assert_eq!(delta.utf16_base_len, 7);
|
||||||
assert_eq!(delta.target_len, 10);
|
assert_eq!(delta.utf16_target_len, 10);
|
||||||
delta.delete(2);
|
delta.delete(2);
|
||||||
assert_eq!(delta.base_len, 9);
|
assert_eq!(delta.utf16_base_len, 9);
|
||||||
assert_eq!(delta.target_len, 10);
|
assert_eq!(delta.utf16_target_len, 10);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn sequence() {
|
fn sequence() {
|
||||||
@ -331,7 +331,7 @@ fn apply_1000() {
|
|||||||
let mut rng = Rng::default();
|
let mut rng = Rng::default();
|
||||||
let s: FlowyStr = rng.gen_string(50).into();
|
let s: FlowyStr = rng.gen_string(50).into();
|
||||||
let delta = rng.gen_delta(&s);
|
let delta = rng.gen_delta(&s);
|
||||||
assert_eq!(s.count_utf16_code_units(), delta.base_len);
|
assert_eq!(s.utf16_size(), delta.utf16_base_len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,8 +372,8 @@ fn invert() {
|
|||||||
let s = rng.gen_string(50);
|
let s = rng.gen_string(50);
|
||||||
let delta_a = rng.gen_delta(&s);
|
let delta_a = rng.gen_delta(&s);
|
||||||
let delta_b = delta_a.invert_str(&s);
|
let delta_b = delta_a.invert_str(&s);
|
||||||
assert_eq!(delta_a.base_len, delta_b.target_len);
|
assert_eq!(delta_a.utf16_base_len, delta_b.utf16_target_len);
|
||||||
assert_eq!(delta_a.target_len, delta_b.base_len);
|
assert_eq!(delta_a.utf16_target_len, delta_b.utf16_base_len);
|
||||||
assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);
|
assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -444,14 +444,14 @@ fn compose() {
|
|||||||
let s = rng.gen_string(20);
|
let s = rng.gen_string(20);
|
||||||
let a = rng.gen_delta(&s);
|
let a = rng.gen_delta(&s);
|
||||||
let after_a: FlowyStr = a.apply(&s).unwrap().into();
|
let after_a: FlowyStr = a.apply(&s).unwrap().into();
|
||||||
assert_eq!(a.target_len, after_a.count_utf16_code_units());
|
assert_eq!(a.utf16_target_len, after_a.utf16_size());
|
||||||
|
|
||||||
let b = rng.gen_delta(&after_a);
|
let b = rng.gen_delta(&after_a);
|
||||||
let after_b: FlowyStr = b.apply(&after_a).unwrap().into();
|
let after_b: FlowyStr = b.apply(&after_a).unwrap().into();
|
||||||
assert_eq!(b.target_len, after_b.count_utf16_code_units());
|
assert_eq!(b.utf16_target_len, after_b.utf16_size());
|
||||||
|
|
||||||
let ab = a.compose(&b).unwrap();
|
let ab = a.compose(&b).unwrap();
|
||||||
assert_eq!(ab.target_len, b.target_len);
|
assert_eq!(ab.utf16_target_len, b.utf16_target_len);
|
||||||
let after_ab: FlowyStr = ab.apply(&s).unwrap().into();
|
let after_ab: FlowyStr = ab.apply(&s).unwrap().into();
|
||||||
assert_eq!(after_b, after_ab);
|
assert_eq!(after_b, after_ab);
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
mod document;
|
||||||
mod editor;
|
mod editor;
|
||||||
|
@ -6,7 +6,7 @@ use flowy_document::{
|
|||||||
core::{DocumentWSReceivers, DocumentWebSocket, WSStateReceiver},
|
core::{DocumentWSReceivers, DocumentWebSocket, WSStateReceiver},
|
||||||
errors::{internal_error, FlowyError},
|
errors::{internal_error, FlowyError},
|
||||||
};
|
};
|
||||||
use flowy_net::services::ws_conn::{FlowyWSSender, FlowyWebSocketConnect};
|
use flowy_net::services::ws_conn::FlowyWebSocketConnect;
|
||||||
use flowy_user::services::user::UserSession;
|
use flowy_user::services::user::UserSession;
|
||||||
use lib_ws::{WSMessageReceiver, WSModule, WebSocketRawMessage};
|
use lib_ws::{WSMessageReceiver, WSModule, WebSocketRawMessage};
|
||||||
use std::{convert::TryInto, path::Path, sync::Arc};
|
use std::{convert::TryInto, path::Path, sync::Arc};
|
||||||
|
@ -10,7 +10,6 @@ flowy-sdk = { path = "../flowy-sdk"}
|
|||||||
flowy-user = { path = "../flowy-user"}
|
flowy-user = { path = "../flowy-user"}
|
||||||
flowy-net = { path = "../flowy-net"}
|
flowy-net = { path = "../flowy-net"}
|
||||||
flowy-core = { path = "../flowy-core", default-features = false}
|
flowy-core = { path = "../flowy-core", default-features = false}
|
||||||
flowy-document = { path = "../flowy-document", features = ["flowy_unit_test"]}
|
|
||||||
lib-dispatch = { path = "../lib-dispatch" }
|
lib-dispatch = { path = "../lib-dispatch" }
|
||||||
|
|
||||||
flowy-collaboration = { path = "../../../shared-lib/flowy-collaboration" }
|
flowy-collaboration = { path = "../../../shared-lib/flowy-collaboration" }
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::{fs, path::PathBuf, sync::Arc};
|
use crate::prelude::*;
|
||||||
|
|
||||||
use flowy_collaboration::entities::doc::DocumentInfo;
|
use flowy_collaboration::entities::doc::DocumentInfo;
|
||||||
use flowy_core::{
|
use flowy_core::{
|
||||||
entities::{
|
entities::{
|
||||||
@ -18,8 +17,7 @@ use flowy_user::{
|
|||||||
};
|
};
|
||||||
use lib_dispatch::prelude::{EventDispatcher, ModuleRequest, ToBytes};
|
use lib_dispatch::prelude::{EventDispatcher, ModuleRequest, ToBytes};
|
||||||
use lib_infra::uuid_string;
|
use lib_infra::uuid_string;
|
||||||
|
use std::{fs, path::PathBuf, sync::Arc};
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub struct WorkspaceTest {
|
pub struct WorkspaceTest {
|
||||||
pub sdk: FlowySDKTest,
|
pub sdk: FlowySDKTest,
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
pub mod doc_script;
|
|
||||||
pub mod event_builder;
|
pub mod event_builder;
|
||||||
pub mod helper;
|
pub mod helper;
|
||||||
|
|
||||||
use crate::helper::*;
|
use crate::helper::*;
|
||||||
use backend_service::configuration::{get_client_server_configuration, ClientServerConfiguration};
|
use backend_service::configuration::{get_client_server_configuration, ClientServerConfiguration};
|
||||||
use flowy_net::services::ws_conn::FlowyRawWebSocket;
|
|
||||||
use flowy_sdk::{FlowySDK, FlowySDKConfig};
|
use flowy_sdk::{FlowySDK, FlowySDKConfig};
|
||||||
use flowy_user::entities::UserProfile;
|
use flowy_user::entities::UserProfile;
|
||||||
use lib_infra::uuid_string;
|
use lib_infra::uuid_string;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::{event_builder::*, helper::*, *};
|
pub use crate::{event_builder::*, helper::*, *};
|
||||||
@ -18,7 +15,6 @@ pub mod prelude {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FlowySDKTest {
|
pub struct FlowySDKTest {
|
||||||
pub inner: FlowySDK,
|
pub inner: FlowySDK,
|
||||||
pub ws: Option<Arc<dyn FlowyRawWebSocket>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for FlowySDKTest {
|
impl std::ops::Deref for FlowySDKTest {
|
||||||
@ -30,18 +26,18 @@ impl std::ops::Deref for FlowySDKTest {
|
|||||||
impl std::default::Default for FlowySDKTest {
|
impl std::default::Default for FlowySDKTest {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let server_config = get_client_server_configuration().unwrap();
|
let server_config = get_client_server_configuration().unwrap();
|
||||||
let sdk = Self::new(server_config, None);
|
let sdk = Self::new(server_config);
|
||||||
std::mem::forget(sdk.dispatcher());
|
std::mem::forget(sdk.dispatcher());
|
||||||
sdk
|
sdk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlowySDKTest {
|
impl FlowySDKTest {
|
||||||
pub fn new(server_config: ClientServerConfiguration, ws: Option<Arc<dyn FlowyRawWebSocket>>) -> Self {
|
pub fn new(server_config: ClientServerConfiguration) -> Self {
|
||||||
let config = FlowySDKConfig::new(&root_dir(), server_config, &uuid_string()).log_filter("debug");
|
let config = FlowySDKConfig::new(&root_dir(), server_config, &uuid_string()).log_filter("debug");
|
||||||
let sdk = FlowySDK::new(config);
|
let sdk = FlowySDK::new(config);
|
||||||
std::mem::forget(sdk.dispatcher());
|
std::mem::forget(sdk.dispatcher());
|
||||||
Self { inner: sdk, ws }
|
Self { inner: sdk }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sign_up(&self) -> SignUpContext {
|
pub async fn sign_up(&self) -> SignUpContext {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
mod revision_test;
|
|
@ -1,28 +0,0 @@
|
|||||||
use flowy_collaboration::entities::revision::RevisionState;
|
|
||||||
use flowy_test::doc_script::{EditorScript::*, *};
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn doc_sync_test() {
|
|
||||||
let scripts = vec![
|
|
||||||
InsertText("1", 0),
|
|
||||||
InsertText("2", 1),
|
|
||||||
InsertText("3", 2),
|
|
||||||
AssertJson(r#"[{"insert":"123\n"}]"#),
|
|
||||||
AssertNextRevId(None),
|
|
||||||
];
|
|
||||||
EditorTest::new().await.run_scripts(scripts).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn doc_sync_retry_ws_conn() {
|
|
||||||
let scripts = vec![
|
|
||||||
InsertText("1", 0),
|
|
||||||
InsertText("2", 1),
|
|
||||||
InsertText("3", 2),
|
|
||||||
AssertRevisionState(2, RevisionState::Ack),
|
|
||||||
AssertRevisionState(3, RevisionState::Ack),
|
|
||||||
AssertNextRevId(None),
|
|
||||||
AssertJson(r#"[{"insert":"123\n"}]"#),
|
|
||||||
];
|
|
||||||
EditorTest::new().await.run_scripts(scripts).await;
|
|
||||||
}
|
|
@ -74,8 +74,7 @@ impl Builder {
|
|||||||
let _ = LogTracer::builder()
|
let _ = LogTracer::builder()
|
||||||
.with_max_level(LevelFilter::Trace)
|
.with_max_level(LevelFilter::Trace)
|
||||||
.init()
|
.init()
|
||||||
.map_err(|e| format!("{:?}", e))
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
*LOG_GUARD.write().unwrap() = Some(guard);
|
*LOG_GUARD.write().unwrap() = Some(guard);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -109,10 +109,10 @@ impl Document {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert<T: ToString>(&mut self, index: usize, data: T) -> Result<RichTextDelta, CollaborateError> {
|
pub fn insert<T: ToString>(&mut self, index: usize, data: T) -> Result<RichTextDelta, CollaborateError> {
|
||||||
|
let text = data.to_string();
|
||||||
let interval = Interval::new(index, index);
|
let interval = Interval::new(index, index);
|
||||||
let _ = validate_interval(&self.delta, &interval)?;
|
let _ = validate_interval(&self.delta, &interval)?;
|
||||||
|
|
||||||
let text = data.to_string();
|
|
||||||
let delta = self.view.insert(&self.delta, &text, interval)?;
|
let delta = self.view.insert(&self.delta, &text, interval)?;
|
||||||
self.compose_delta(delta.clone())?;
|
self.compose_delta(delta.clone())?;
|
||||||
Ok(delta)
|
Ok(delta)
|
||||||
@ -201,8 +201,8 @@ impl Document {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate_interval(delta: &RichTextDelta, interval: &Interval) -> Result<(), CollaborateError> {
|
fn validate_interval(delta: &RichTextDelta, interval: &Interval) -> Result<(), CollaborateError> {
|
||||||
if delta.target_len < interval.end {
|
if delta.utf16_target_len < interval.end {
|
||||||
log::error!("{:?} out of bounds. should 0..{}", interval, delta.target_len);
|
log::error!("{:?} out of bounds. should 0..{}", interval, delta.utf16_target_len);
|
||||||
return Err(CollaborateError::out_of_bound());
|
return Err(CollaborateError::out_of_bound());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -17,8 +17,8 @@ use std::{
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Delta<T: Attributes> {
|
pub struct Delta<T: Attributes> {
|
||||||
pub ops: Vec<Operation<T>>,
|
pub ops: Vec<Operation<T>>,
|
||||||
pub base_len: usize,
|
pub utf16_base_len: usize,
|
||||||
pub target_len: usize,
|
pub utf16_target_len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for Delta<T>
|
impl<T> Default for Delta<T>
|
||||||
@ -28,8 +28,8 @@ where
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ops: Vec::new(),
|
ops: Vec::new(),
|
||||||
base_len: 0,
|
utf16_base_len: 0,
|
||||||
target_len: 0,
|
utf16_target_len: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,8 +72,8 @@ where
|
|||||||
pub fn with_capacity(capacity: usize) -> Self {
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ops: Vec::with_capacity(capacity),
|
ops: Vec::with_capacity(capacity),
|
||||||
base_len: 0,
|
utf16_base_len: 0,
|
||||||
target_len: 0,
|
utf16_target_len: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ where
|
|||||||
if n == 0 {
|
if n == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.base_len += n as usize;
|
self.utf16_base_len += n as usize;
|
||||||
if let Some(Operation::Delete(n_last)) = self.ops.last_mut() {
|
if let Some(Operation::Delete(n_last)) = self.ops.last_mut() {
|
||||||
*n_last += n;
|
*n_last += n;
|
||||||
} else {
|
} else {
|
||||||
@ -103,7 +103,7 @@ where
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.target_len += s.count_utf16_code_units();
|
self.utf16_target_len += s.utf16_size();
|
||||||
let new_last = match self.ops.as_mut_slice() {
|
let new_last = match self.ops.as_mut_slice() {
|
||||||
[.., Operation::<T>::Insert(insert)] => {
|
[.., Operation::<T>::Insert(insert)] => {
|
||||||
//
|
//
|
||||||
@ -131,8 +131,8 @@ where
|
|||||||
if n == 0 {
|
if n == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.base_len += n as usize;
|
self.utf16_base_len += n as usize;
|
||||||
self.target_len += n as usize;
|
self.utf16_target_len += n as usize;
|
||||||
|
|
||||||
if let Some(Operation::<T>::Retain(retain)) = self.ops.last_mut() {
|
if let Some(Operation::<T>::Retain(retain)) = self.ops.last_mut() {
|
||||||
if let Some(new_op) = retain.merge_or_new(n, attributes) {
|
if let Some(new_op) = retain.merge_or_new(n, attributes) {
|
||||||
@ -146,11 +146,11 @@ where
|
|||||||
/// Applies an operation to a string, returning a new string.
|
/// Applies an operation to a string, returning a new string.
|
||||||
pub fn apply(&self, s: &str) -> Result<String, OTError> {
|
pub fn apply(&self, s: &str) -> Result<String, OTError> {
|
||||||
let s: FlowyStr = s.into();
|
let s: FlowyStr = s.into();
|
||||||
if s.count_utf16_code_units() != self.base_len {
|
if s.utf16_size() != self.utf16_base_len {
|
||||||
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build());
|
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build());
|
||||||
}
|
}
|
||||||
let mut new_s = String::new();
|
let mut new_s = String::new();
|
||||||
let code_point_iter = &mut s.code_point_iter();
|
let code_point_iter = &mut s.utf16_code_unit_iter();
|
||||||
for op in &self.ops {
|
for op in &self.ops {
|
||||||
match &op {
|
match &op {
|
||||||
Operation::Retain(retain) => {
|
Operation::Retain(retain) => {
|
||||||
@ -271,11 +271,11 @@ where
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
if self.base_len != other.base_len {
|
if self.utf16_base_len != other.utf16_base_len {
|
||||||
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength)
|
return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength)
|
||||||
.msg(format!(
|
.msg(format!(
|
||||||
"cur base length: {}, other base length: {}",
|
"cur base length: {}, other base length: {}",
|
||||||
self.base_len, other.base_len
|
self.utf16_base_len, other.utf16_base_len
|
||||||
))
|
))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,9 @@ pub struct FlowyStr(pub String);
|
|||||||
|
|
||||||
impl FlowyStr {
|
impl FlowyStr {
|
||||||
// https://stackoverflow.com/questions/2241348/what-is-unicode-utf-8-utf-16
|
// https://stackoverflow.com/questions/2241348/what-is-unicode-utf-8-utf-16
|
||||||
pub fn count_utf16_code_units(&self) -> usize { count_utf16_code_units(&self.0) }
|
pub fn utf16_size(&self) -> usize { count_utf16_code_units(&self.0) }
|
||||||
|
|
||||||
pub fn utf16_iter(&self) -> FlowyUtf16CodePointIterator { FlowyUtf16CodePointIterator::new(self, 0) }
|
pub fn utf16_code_unit_iter(&self) -> Utf16CodeUnitIterator { Utf16CodeUnitIterator::new(self) }
|
||||||
|
|
||||||
pub fn code_point_iter(&self) -> Utf16CodeUnitIterator { Utf16CodeUnitIterator::new(self) }
|
|
||||||
|
|
||||||
pub fn sub_str(&self, interval: Interval) -> String {
|
pub fn sub_str(&self, interval: Interval) -> String {
|
||||||
match self.with_interval(interval) {
|
match self.with_interval(interval) {
|
||||||
@ -23,7 +21,7 @@ impl FlowyStr {
|
|||||||
let mut iter = Utf16CodeUnitIterator::new(self);
|
let mut iter = Utf16CodeUnitIterator::new(self);
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
while let Some((byte, _len)) = iter.next() {
|
while let Some((byte, _len)) = iter.next() {
|
||||||
if interval.start < iter.code_point_offset && interval.end >= iter.code_point_offset {
|
if interval.start < iter.code_unit_offset && interval.end >= iter.code_unit_offset {
|
||||||
buf.extend_from_slice(byte);
|
buf.extend_from_slice(byte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,12 +35,15 @@ impl FlowyStr {
|
|||||||
Err(_e) => None,
|
Err(_e) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn utf16_code_point_iter(&self) -> FlowyUtf16CodePointIterator { FlowyUtf16CodePointIterator::new(self, 0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Utf16CodeUnitIterator<'a> {
|
pub struct Utf16CodeUnitIterator<'a> {
|
||||||
s: &'a FlowyStr,
|
s: &'a FlowyStr,
|
||||||
bytes_offset: usize,
|
bytes_offset: usize,
|
||||||
code_point_offset: usize,
|
code_unit_offset: usize,
|
||||||
iter_index: usize,
|
iter_index: usize,
|
||||||
iter: slice::Iter<'a, u8>,
|
iter: slice::Iter<'a, u8>,
|
||||||
}
|
}
|
||||||
@ -52,7 +53,7 @@ impl<'a> Utf16CodeUnitIterator<'a> {
|
|||||||
Utf16CodeUnitIterator {
|
Utf16CodeUnitIterator {
|
||||||
s,
|
s,
|
||||||
bytes_offset: 0,
|
bytes_offset: 0,
|
||||||
code_point_offset: 0,
|
code_unit_offset: 0,
|
||||||
iter_index: 0,
|
iter_index: 0,
|
||||||
iter: s.as_bytes().iter(),
|
iter: s.as_bytes().iter(),
|
||||||
}
|
}
|
||||||
@ -69,7 +70,7 @@ impl<'a> Iterator for Utf16CodeUnitIterator<'a> {
|
|||||||
while let Some(&b) = self.iter.next() {
|
while let Some(&b) = self.iter.next() {
|
||||||
self.iter_index += 1;
|
self.iter_index += 1;
|
||||||
|
|
||||||
let mut code_point_count = 0;
|
let mut code_unit_count = 0;
|
||||||
if self.bytes_offset > self.iter_index {
|
if self.bytes_offset > self.iter_index {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -79,16 +80,16 @@ impl<'a> Iterator for Utf16CodeUnitIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (b as i8) >= -0x40 {
|
if (b as i8) >= -0x40 {
|
||||||
code_point_count += 1
|
code_unit_count += 1
|
||||||
}
|
}
|
||||||
if b >= 0xf0 {
|
if b >= 0xf0 {
|
||||||
code_point_count += 1
|
code_unit_count += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bytes_offset += len_utf8_from_first_byte(b);
|
self.bytes_offset += len_utf8_from_first_byte(b);
|
||||||
self.code_point_offset += code_point_count;
|
self.code_unit_offset += code_unit_count;
|
||||||
|
|
||||||
if code_point_count == 1 {
|
if code_unit_count == 1 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +202,6 @@ impl<'a> Iterator for FlowyUtf16CodePointIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_utf16_code_units(s: &str) -> usize {
|
pub fn count_utf16_code_units(s: &str) -> usize {
|
||||||
// bytecount::num_chars(s.as_bytes())
|
|
||||||
let mut utf16_count = 0;
|
let mut utf16_count = 0;
|
||||||
for &b in s.as_bytes() {
|
for &b in s.as_bytes() {
|
||||||
if (b as i8) >= -0x40 {
|
if (b as i8) >= -0x40 {
|
||||||
@ -231,9 +231,9 @@ mod tests {
|
|||||||
use crate::core::{FlowyStr, Interval};
|
use crate::core::{FlowyStr, Interval};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flowy_str_utf16_test() {
|
fn flowy_str_utf16_code_point_iter_test1() {
|
||||||
let s: FlowyStr = "👋😁👋😁".into();
|
let s: FlowyStr = "👋😁👋😁".into();
|
||||||
let mut iter = s.utf16_iter();
|
let mut iter = s.utf16_code_point_iter();
|
||||||
assert_eq!(iter.next().unwrap(), "👋".to_string());
|
assert_eq!(iter.next().unwrap(), "👋".to_string());
|
||||||
assert_eq!(iter.next().unwrap(), "😁".to_string());
|
assert_eq!(iter.next().unwrap(), "😁".to_string());
|
||||||
assert_eq!(iter.next().unwrap(), "👋".to_string());
|
assert_eq!(iter.next().unwrap(), "👋".to_string());
|
||||||
@ -242,15 +242,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flowy_str_utf16_iter_test() {
|
fn flowy_str_utf16_code_point_iter_test2() {
|
||||||
let s: FlowyStr = "👋👋😁😁👋👋".into();
|
let s: FlowyStr = "👋👋😁😁👋👋".into();
|
||||||
let iter = s.utf16_iter();
|
let iter = s.utf16_code_point_iter();
|
||||||
let result = iter.skip(2).take(2).collect::<String>();
|
let result = iter.skip(2).take(2).collect::<String>();
|
||||||
assert_eq!(result, "😁😁".to_string());
|
assert_eq!(result, "😁😁".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flowy_str_code_point_test() {
|
fn flowy_str_code_unit_test() {
|
||||||
let s: FlowyStr = "👋 \n👋".into();
|
let s: FlowyStr = "👋 \n👋".into();
|
||||||
let output = s.with_interval(Interval::new(0, 2)).unwrap().0;
|
let output = s.with_interval(Interval::new(0, 2)).unwrap().0;
|
||||||
assert_eq!(output, "👋");
|
assert_eq!(output, "👋");
|
||||||
|
@ -291,7 +291,7 @@ impl<T> Insert<T>
|
|||||||
where
|
where
|
||||||
T: Attributes,
|
T: Attributes,
|
||||||
{
|
{
|
||||||
pub fn count_of_utf16_code_units(&self) -> usize { self.s.count_utf16_code_units() }
|
pub fn count_of_utf16_code_units(&self) -> usize { self.s.utf16_size() }
|
||||||
|
|
||||||
pub fn merge_or_new_op(&mut self, s: &str, attributes: T) -> Option<Operation<T>> {
|
pub fn merge_or_new_op(&mut self, s: &str, attributes: T) -> Option<Operation<T>> {
|
||||||
if self.attributes == attributes {
|
if self.attributes == attributes {
|
||||||
|
Reference in New Issue
Block a user