From 75614483235ccf966997576b90c10986651b3ddc Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Wed, 3 Apr 2024 17:56:01 +0800 Subject: [PATCH] refactor: replace "local" and "fromCheckout" in event with "triggeredBy" (#312) * refactor: replace "local" and "fromCheckout" in event with "triggeredBy" * refactor: simplify the name to `by` --- crates/loro-internal/scripts/fuzz.ts | 7 +- crates/loro-internal/src/event.rs | 50 +++++++++++--- crates/loro-internal/src/handler.rs | 2 +- crates/loro-internal/src/loro.rs | 11 ++- crates/loro-internal/src/state.rs | 24 +++---- crates/loro-internal/src/txn.rs | 5 +- crates/loro-internal/tests/test.rs | 9 ++- crates/loro-wasm/scripts/build.ts | 2 +- crates/loro-wasm/src/lib.rs | 21 +++--- crates/loro/src/event.rs | 7 +- crates/loro/src/lib.rs | 2 +- crates/loro/tests/loro_rust_test.rs | 5 +- examples/loro-quill/src/binding.ts | 35 +++++----- loro-js/src/index.ts | 12 ++-- loro-js/tests/checkout.test.ts | 26 ++++--- loro-js/tests/event.test.ts | 46 +++++++++++- loro-js/tests/issue.test.ts | 100 +++++++++++++-------------- loro-js/tests/misc.test.ts | 8 +-- loro-js/tests/richtext.test.ts | 33 +++++---- loro-js/tests/version.test.ts | 48 ++++++++----- 20 files changed, 275 insertions(+), 178 deletions(-) diff --git a/crates/loro-internal/scripts/fuzz.ts b/crates/loro-internal/scripts/fuzz.ts index 53536def..86f7f100 100644 --- a/crates/loro-internal/scripts/fuzz.ts +++ b/crates/loro-internal/scripts/fuzz.ts @@ -6,9 +6,10 @@ const validTargets = Array.from( Deno.readDirSync(resolve(__dirname, "../fuzz/fuzz_targets")), ).map((x) => x.name.replace(/.rs$/, "")); -const targets = Deno.args.length === 0 - ? validTargets - : Deno.args.filter((x) => validTargets.includes(x)); +const targets = + Deno.args.length === 0 + ? validTargets + : Deno.args.filter((x) => validTargets.includes(x)); const promises = []; for (const target of targets) { diff --git a/crates/loro-internal/src/event.rs b/crates/loro-internal/src/event.rs index a1c61194..7a8247f3 100644 --- a/crates/loro-internal/src/event.rs +++ b/crates/loro-internal/src/event.rs @@ -30,6 +30,44 @@ pub struct ContainerDiff { pub diff: Diff, } +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum EventTriggerKind { + /// The event is triggered by a local transaction. + Local, + /// The event is triggered by importing + Import, + /// The event is triggered by checkout + Checkout, +} + +impl std::fmt::Display for EventTriggerKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EventTriggerKind::Local => write!(f, "local"), + EventTriggerKind::Import => write!(f, "import"), + EventTriggerKind::Checkout => write!(f, "checkout"), + } + } +} + +impl EventTriggerKind { + #[inline] + pub fn is_local(&self) -> bool { + matches!(self, EventTriggerKind::Local) + } + + #[inline] + pub fn is_import(&self) -> bool { + matches!(self, EventTriggerKind::Import) + } + + #[inline] + pub fn is_checkout(&self) -> bool { + matches!(self, EventTriggerKind::Checkout) + } +} + #[derive(Debug, Clone)] pub struct DiffEvent<'a> { /// The receiver of the event. @@ -49,9 +87,7 @@ pub struct DocDiff { pub from: Frontiers, pub to: Frontiers, pub origin: InternalString, - pub local: bool, - /// Whether the diff is created from the checkout operation. - pub from_checkout: bool, + pub by: EventTriggerKind, pub diff: Vec, } @@ -89,8 +125,7 @@ pub(crate) enum DiffVariant { #[derive(Debug, Clone)] pub(crate) struct InternalDocDiff<'a> { pub(crate) origin: InternalString, - pub(crate) local: bool, - pub(crate) from_checkout: bool, + pub(crate) by: EventTriggerKind, pub(crate) diff: Cow<'a, [InternalContainerDiff]>, pub(crate) new_version: Cow<'a, Frontiers>, } @@ -99,15 +134,14 @@ impl<'a> InternalDocDiff<'a> { pub fn into_owned(self) -> InternalDocDiff<'static> { InternalDocDiff { origin: self.origin, - local: self.local, - from_checkout: self.from_checkout, + by: self.by, diff: Cow::Owned((*self.diff).to_owned()), new_version: Cow::Owned((*self.new_version).to_owned()), } } pub fn can_merge(&self, other: &Self) -> bool { - self.origin == other.origin && self.local == other.local + self.by == other.by } } diff --git a/crates/loro-internal/src/handler.rs b/crates/loro-internal/src/handler.rs index 15eb2aeb..ec3e766a 100644 --- a/crates/loro-internal/src/handler.rs +++ b/crates/loro-internal/src/handler.rs @@ -2477,7 +2477,7 @@ mod test { let loro2 = LoroDoc::new(); loro2.subscribe_root(Arc::new(|e| { - println!("{} {:?} ", e.event_meta.local, e.event_meta.diff) + println!("{} {:?} ", e.event_meta.by, e.event_meta.diff) })); loro2.import(&loro.export_from(&loro2.oplog_vv())).unwrap(); assert_eq!(loro.get_deep_value(), loro2.get_deep_value()); diff --git a/crates/loro-internal/src/loro.rs b/crates/loro-internal/src/loro.rs index a7f805ca..67f846a0 100644 --- a/crates/loro-internal/src/loro.rs +++ b/crates/loro-internal/src/loro.rs @@ -21,7 +21,7 @@ use crate::{ encoding::{ decode_snapshot, export_snapshot, parse_header_and_body, EncodeMode, ParsedHeaderAndBody, }, - event::{str_to_path, Index}, + event::{str_to_path, EventTriggerKind, Index}, handler::{Handler, TextHandler, TreeHandler, ValueOrHandler}, id::PeerID, oplog::dag::FrontiersNotIncluded, @@ -471,9 +471,8 @@ impl LoroDoc { let mut state = self.state.lock().unwrap(); state.apply_diff(InternalDocDiff { origin, - local: false, diff: (diff).into(), - from_checkout: false, + by: EventTriggerKind::Import, new_version: Cow::Owned(oplog.frontiers().clone()), }); } else { @@ -507,9 +506,8 @@ impl LoroDoc { let mut state = self.state.lock().unwrap(); state.apply_diff(InternalDocDiff { origin: "".into(), - local: false, diff: (diff).into(), - from_checkout: false, + by: EventTriggerKind::Import, new_version: Cow::Owned(oplog.frontiers().clone()), }); } @@ -784,9 +782,8 @@ impl LoroDoc { ); state.apply_diff(InternalDocDiff { origin: "checkout".into(), - local: true, + by: EventTriggerKind::Checkout, diff: Cow::Owned(diff), - from_checkout: true, new_version: Cow::Owned(frontiers.clone()), }); drop(state); diff --git a/crates/loro-internal/src/state.rs b/crates/loro-internal/src/state.rs index e9765328..360ed703 100644 --- a/crates/loro-internal/src/state.rs +++ b/crates/loro-internal/src/state.rs @@ -16,7 +16,7 @@ use crate::{ }, delta::DeltaItem, encoding::{StateSnapshotDecodeContext, StateSnapshotEncoder}, - event::{Diff, Index, InternalContainerDiff, InternalDiff}, + event::{Diff, EventTriggerKind, Index, InternalContainerDiff, InternalDiff}, fx_map, handler::ValueOrHandler, id::PeerID, @@ -310,7 +310,7 @@ impl DocState { } /// This should be called when DocState is going to apply a transaction / a diff. - fn pre_txn(&mut self, next_origin: InternalString, next_local: bool) { + fn pre_txn(&mut self, next_origin: InternalString, next_trigger: EventTriggerKind) { if !self.is_recording() { return; } @@ -319,7 +319,7 @@ impl DocState { return; }; - if last_diff.origin == next_origin && last_diff.local == next_local { + if last_diff.origin == next_origin && last_diff.by == next_trigger { return; } @@ -359,7 +359,7 @@ impl DocState { } // tracing::info!("Diff = {:#?}", &diff); let is_recording = self.is_recording(); - self.pre_txn(diff.origin.clone(), diff.local); + self.pre_txn(diff.origin.clone(), diff.by); let Cow::Owned(inner) = std::mem::take(&mut diff.diff) else { unreachable!() }; @@ -480,8 +480,8 @@ impl DocState { state.apply_local_op(raw_op, op) } - pub(crate) fn start_txn(&mut self, origin: InternalString, local: bool) { - self.pre_txn(origin, local); + pub(crate) fn start_txn(&mut self, origin: InternalString, trigger: EventTriggerKind) { + self.pre_txn(origin, trigger); self.in_txn = true; } @@ -626,7 +626,7 @@ impl DocState { frontiers: Frontiers, ) { assert!(self.states.is_empty(), "overriding states"); - self.pre_txn(Default::default(), false); + self.pre_txn(Default::default(), EventTriggerKind::Import); self.states = states; for (idx, state) in self.states.iter() { for child_id in state.get_child_containers() { @@ -652,8 +652,7 @@ impl DocState { .collect(); self.record_diff(InternalDocDiff { origin: Default::default(), - local: false, - from_checkout: false, + by: EventTriggerKind::Import, diff, new_version: Cow::Borrowed(&frontiers), }); @@ -896,11 +895,11 @@ impl DocState { panic!("diffs is empty"); } + let triggered_by = diffs[0].by; + assert!(diffs.iter().all(|x| x.by == triggered_by)); let mut containers = FxHashMap::default(); let to = (*diffs.last().unwrap().new_version).to_owned(); let origin = diffs[0].origin.clone(); - let local = diffs[0].local; - let from_checkout = diffs[0].from_checkout; for diff in diffs { #[allow(clippy::unnecessary_to_owned)] for container_diff in diff.diff.into_owned() { @@ -950,8 +949,7 @@ impl DocState { from, to, origin, - from_checkout, - local, + by: triggered_by, diff, } } diff --git a/crates/loro-internal/src/txn.rs b/crates/loro-internal/src/txn.rs index d11161b9..b867452d 100644 --- a/crates/loro-internal/src/txn.rs +++ b/crates/loro-internal/src/txn.rs @@ -203,7 +203,7 @@ impl Transaction { } let oplog_lock = oplog.lock().unwrap(); - state_lock.start_txn(origin, true); + state_lock.start_txn(origin, crate::event::EventTriggerKind::Local); let arena = state_lock.arena.clone(); let frontiers = state_lock.frontiers.clone(); let peer = state_lock.peer; @@ -300,7 +300,7 @@ impl Transaction { state.commit_txn( Frontiers::from_id(last_id), diff.map(|arr| InternalDocDiff { - local: true, + by: crate::event::EventTriggerKind::Local, origin: self.origin.clone(), diff: Cow::Owned( arr.into_iter() @@ -312,7 +312,6 @@ impl Transaction { }) .collect(), ), - from_checkout: false, new_version: Cow::Borrowed(oplog.frontiers()), }), ); diff --git a/crates/loro-internal/tests/test.rs b/crates/loro-internal/tests/test.rs index d8221e65..cfae8cb1 100644 --- a/crates/loro-internal/tests/test.rs +++ b/crates/loro-internal/tests/test.rs @@ -4,7 +4,7 @@ use fxhash::FxHashMap; use loro_common::{ContainerID, ContainerType, LoroResult, LoroValue, ID}; use loro_internal::{ delta::ResolvedMapValue, - event::Diff, + event::{Diff, EventTriggerKind}, handler::{Handler, TextDelta, ValueOrHandler}, version::Frontiers, ApplyDiff, HandlerTrait, ListHandler, LoroDoc, MapHandler, TextHandler, ToJson, @@ -94,7 +94,10 @@ fn mark_with_the_same_key_value_should_be_skipped() { fn event_from_checkout() { let a = LoroDoc::new_auto_commit(); let sub_id = a.subscribe_root(Arc::new(|event| { - assert!(!event.event_meta.from_checkout); + assert!(matches!( + event.event_meta.by, + EventTriggerKind::Checkout | EventTriggerKind::Local + )); })); a.get_text("text").insert(0, "hello").unwrap(); a.commit_then_renew(); @@ -105,7 +108,7 @@ fn event_from_checkout() { let ran = Arc::new(AtomicBool::new(false)); let ran_cloned = ran.clone(); a.subscribe_root(Arc::new(move |event| { - assert!(event.event_meta.from_checkout); + assert!(event.event_meta.by.is_checkout()); ran.store(true, std::sync::atomic::Ordering::Relaxed); })); a.checkout(&version).unwrap(); diff --git a/crates/loro-wasm/scripts/build.ts b/crates/loro-wasm/scripts/build.ts index 4a35c124..b98959a7 100644 --- a/crates/loro-wasm/scripts/build.ts +++ b/crates/loro-wasm/scripts/build.ts @@ -37,7 +37,7 @@ async function build() { if (profile !== "dev") { await Promise.all( TARGETS.map(async (target) => { - // --snip-rust-panicking-code --snip-rust-fmt-code + // --snip-rust-panicking-code --snip-rust-fmt-code const snip = `wasm-snip ./${target}/loro_wasm_bg.wasm -o ./${target}/loro_wasm_bg.wasm`; console.log(">", snip); await Deno.run({ cmd: snip.split(" "), cwd: LoroWasmDir }).status(); diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index 4cfa22f4..a71ad65e 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -61,6 +61,8 @@ pub struct Loro(Arc); extern "C" { #[wasm_bindgen(typescript_type = "number | bigint | `${number}`")] pub type JsIntoPeerID; + #[wasm_bindgen(typescript_type = "`${number}`")] + pub type JsStrPeerID; #[wasm_bindgen(typescript_type = "ContainerID")] pub type JsContainerID; #[wasm_bindgen(typescript_type = "ContainerID | string")] @@ -468,8 +470,9 @@ impl Loro { /// Get peer id in decimal string. #[wasm_bindgen(js_name = "peerIdStr", method, getter)] - pub fn peer_id_str(&self) -> String { - format!("{}", self.0.peer_id()) + pub fn peer_id_str(&self) -> JsStrPeerID { + let v: JsValue = format!("{}", self.0.peer_id()).into(); + v.into() } /// Set the peer ID of the current writer. @@ -1106,9 +1109,7 @@ fn call_subscriber(ob: observer::Observer, e: DiffEvent, doc: &Arc) { // [1]: https://caniuse.com/?search=FinalizationRegistry // [2]: https://rustwasm.github.io/wasm-bindgen/reference/weak-references.html let event = diff_event_to_js_value(e, doc); - if let Err(e) = ob.call1(&event) { - console_error!("Error when calling observer: {:#?}", e); - } + ob.call1(&event).unwrap_throw(); } #[allow(unused)] @@ -1138,13 +1139,7 @@ impl Default for Loro { fn diff_event_to_js_value(event: DiffEvent, doc: &Arc) -> JsValue { let obj = js_sys::Object::new(); - Reflect::set(&obj, &"local".into(), &event.event_meta.local.into()).unwrap(); - Reflect::set( - &obj, - &"fromCheckout".into(), - &event.event_meta.from_checkout.into(), - ) - .unwrap(); + Reflect::set(&obj, &"by".into(), &event.event_meta.by.to_string().into()).unwrap(); let origin: &str = &event.event_meta.origin; Reflect::set(&obj, &"origin".into(), &JsValue::from_str(origin)).unwrap(); if let Some(t) = event.current_target.as_ref() { @@ -2765,7 +2760,7 @@ export interface ImportBlobMetadata { partialStartVersionVector: VersionVector; /** * The version vector of the end of the import. - * + * * Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`. * However, it does not constitute a complete version vector, as it only contains counters * from peers included within the import blob. diff --git a/crates/loro/src/event.rs b/crates/loro/src/event.rs index c2aea74a..2045f25b 100644 --- a/crates/loro/src/event.rs +++ b/crates/loro/src/event.rs @@ -1,6 +1,7 @@ use enum_as_inner::EnumAsInner; use loro_internal::container::ContainerID; use loro_internal::delta::{DeltaItem, TreeDiff}; +use loro_internal::event::EventTriggerKind; use loro_internal::handler::{TextDelta, ValueOrHandler}; use loro_internal::FxHashMap; use loro_internal::{ @@ -15,8 +16,7 @@ pub type Subscriber = Arc Fn(DiffEvent<'a>)) + Send + Sync>; #[derive(Debug)] pub struct DiffEvent<'a> { - pub local: bool, - pub from_checkout: bool, + pub triggered_by: EventTriggerKind, pub origin: &'a str, pub current_target: Option, pub events: Vec>, @@ -52,8 +52,7 @@ pub struct MapDelta<'a> { impl<'a> From> for DiffEvent<'a> { fn from(value: DiffEventInner<'a>) -> Self { DiffEvent { - local: value.event_meta.local, - from_checkout: value.event_meta.from_checkout, + triggered_by: value.event_meta.by, origin: &value.event_meta.origin, current_target: value.current_target, events: value.events.iter().map(|&diff| diff.into()).collect(), diff --git a/crates/loro/src/lib.rs b/crates/loro/src/lib.rs index 5ddc8e34..1d14bfab 100644 --- a/crates/loro/src/lib.rs +++ b/crates/loro/src/lib.rs @@ -315,7 +315,7 @@ impl LoroDoc { /// doc.subscribe( /// &text.id(), /// Arc::new(move |event| { - /// assert!(event.local); + /// assert!(event.triggered_by.is_local()); /// for event in event.events { /// let delta = event.diff.as_text().unwrap(); /// let d = TextDelta::Insert { diff --git a/crates/loro/tests/loro_rust_test.rs b/crates/loro/tests/loro_rust_test.rs index e2186647..44a52556 100644 --- a/crates/loro/tests/loro_rust_test.rs +++ b/crates/loro/tests/loro_rust_test.rs @@ -376,7 +376,10 @@ fn subscribe() { doc.subscribe( &text.id(), Arc::new(move |event| { - assert!(event.local); + assert!(matches!( + event.triggered_by, + loro_internal::event::EventTriggerKind::Local + )); for event in event.events { let delta = event.diff.as_text().unwrap(); let d = TextDelta::Insert { diff --git a/examples/loro-quill/src/binding.ts b/examples/loro-quill/src/binding.ts index 2b410280..0f20d6cd 100644 --- a/examples/loro-quill/src/binding.ts +++ b/examples/loro-quill/src/binding.ts @@ -11,13 +11,14 @@ import isEqual from "is-equal"; const Delta = Quill.import("delta"); // setDebug("*"); -const EXPAND_CONFIG: { [key in string]: 'before' | 'after' | 'both' | 'none' } = { - bold: 'after', - italic: 'after', - underline: 'after', - link: 'none', - header: 'none', -} +const EXPAND_CONFIG: { [key in string]: "before" | "after" | "both" | "none" } = + { + bold: "after", + italic: "after", + underline: "after", + link: "none", + header: "none", + }; export class QuillBinding { private richtext: LoroText; @@ -31,7 +32,7 @@ export class QuillBinding { underline: { expand: "after" }, link: { expand: "none" }, header: { expand: "none" }, - }) + }); this.quill = quill; this.richtext = doc.getText("text"); this.richtext.subscribe(doc, (event) => { @@ -122,9 +123,9 @@ export class QuillBinding { for (const key of Object.keys(op.attributes)) { let value = op.attributes[key]; if (value == null) { - this.richtext.unmark({ start: index, end, }, key) + this.richtext.unmark({ start: index, end }, key); } else { - this.richtext.mark({ start: index, end }, key, value,) + this.richtext.mark({ start: index, end }, key, value); } } } @@ -137,20 +138,20 @@ export class QuillBinding { for (const key of Object.keys(op.attributes)) { let value = op.attributes[key]; if (value == null) { - this.richtext.unmark({ start: index, end, }, key) + this.richtext.unmark({ start: index, end }, key); } else { - this.richtext.mark({ start: index, end }, key, value) + this.richtext.mark({ start: index, end }, key, value); } } } index = end; } else { - throw new Error("Not implemented") + throw new Error("Not implemented"); } } else if (op.delete != null) { this.richtext.delete(index, op.delete); } else { - throw new Error("Unreachable") + throw new Error("Unreachable"); } } this.doc.commit(); @@ -172,7 +173,7 @@ function assertEqual(a: Delta[], b: Delta[]): boolean { /** * Removes the ending '\n's if it has no attributes. - * + * * Extract line-break to a single op * * Normalize attributes field @@ -212,7 +213,7 @@ export const normQuillDelta = (delta: Delta[]) => { } } - const ans: Delta[] = [] + const ans: Delta[] = []; for (const span of delta) { if (span.insert != null && span.insert.includes("\n")) { const lines = span.insert.split("\n"); @@ -224,7 +225,7 @@ export const normQuillDelta = (delta: Delta[]) => { if (i < lines.length - 1) { const attr = { ...span.attributes }; const v: Delta = { insert: "\n" }; - for (const style of ['bold', 'link', 'italic', 'underline']) { + for (const style of ["bold", "link", "italic", "underline"]) { if (attr && attr[style]) { delete attr[style]; } diff --git a/loro-js/src/index.ts b/loro-js/src/index.ts index d1ae3988..47f1faff 100644 --- a/loro-js/src/index.ts +++ b/loro-js/src/index.ts @@ -8,7 +8,6 @@ import { LoroMap, LoroText, LoroTree, - LoroTreeNode, OpId, TreeID, Value, @@ -56,18 +55,21 @@ export type Path = (number | string | TreeID)[]; /** * A batch of events that created by a single `import`/`transaction`/`checkout`. * - * @prop local - Indicates whether the event is local. + * @prop by - How the event is triggered. * @prop origin - (Optional) Provides information about the origin of the event. * @prop diff - Contains the differential information related to the event. * @prop target - Identifies the container ID of the event's target. * @prop path - Specifies the absolute path of the event's emitter, which can be an index of a list container or a key of a map container. */ export interface LoroEventBatch { - local: boolean; /** - * If true, this event was triggered by a checkout. + * How the event is triggered. + * + * - `local`: The event is triggered by a local transaction. + * - `import`: The event is triggered by an import operation. + * - `checkout`: The event is triggered by a checkout operation. */ - fromCheckout: boolean; + by: "local" | "import" | "checkout"; origin?: string; /** * The container ID of the current event receiver. diff --git a/loro-js/tests/checkout.test.ts b/loro-js/tests/checkout.test.ts index d636720b..a41fb05c 100644 --- a/loro-js/tests/checkout.test.ts +++ b/loro-js/tests/checkout.test.ts @@ -1,27 +1,33 @@ import { describe, expect, it } from "vitest"; -import { - Loro, -} from "../src"; +import { Loro } from "../src"; describe("Checkout", () => { - it("simple checkout", () => { + it("simple checkout", async () => { const doc = new Loro(); doc.setPeerId(0n); const text = doc.getText("text"); text.insert(0, "H"); doc.commit(); + let triggered = false; + doc.subscribe((e) => { + expect(e.by).not.toBe("import"); + expect(e.by === "checkout" || e.by === "local").toBeTruthy(); + triggered = true; + }); const v = doc.frontiers(); text.insert(1, "i"); expect(doc.toJson()).toStrictEqual({ - text: "Hi" + text: "Hi", }); expect(doc.isDetached()).toBeFalsy(); doc.checkout([{ peer: "0", counter: 0 }]); expect(doc.isDetached()).toBeTruthy(); expect(doc.toJson()).toStrictEqual({ - text: "H" + text: "H", }); + await new Promise((resolve) => setTimeout(resolve, 1)); + expect(triggered).toBeTruthy(); }); it("Chinese char", () => { @@ -34,19 +40,19 @@ describe("Checkout", () => { v[0].counter -= 1; doc.checkout(v); expect(doc.toJson()).toStrictEqual({ - text: "你好世" + text: "你好世", }); v[0].counter -= 1; doc.checkout(v); expect(doc.toJson()).toStrictEqual({ - text: "你好" + text: "你好", }); v[0].counter -= 1; doc.checkout(v); expect(doc.toJson()).toStrictEqual({ - text: "你" + text: "你", }); - }) + }); it("two clients", () => { const doc = new Loro(); diff --git a/loro-js/tests/event.test.ts b/loro-js/tests/event.test.ts index bc706019..e2213fd7 100644 --- a/loro-js/tests/event.test.ts +++ b/loro-js/tests/event.test.ts @@ -17,6 +17,7 @@ describe("event", () => { const loro = new Loro(); let lastEvent: undefined | LoroEventBatch; loro.subscribe((event) => { + expect(event.by).toBe("local"); lastEvent = event; }); const text = loro.getText("text"); @@ -131,7 +132,6 @@ describe("event", () => { const loro = new Loro(); let lastEvent: undefined | LoroEventBatch; loro.subscribe((event) => { - console.log(event); lastEvent = event; }); const tree = loro.getTree("tree"); @@ -328,6 +328,50 @@ describe("event", () => { await new Promise((resolve) => setTimeout(resolve, 1)); expect(ran).toBeTruthy(); }); + + it("remote event", async () => { + const doc = new Loro(); + const list = doc.getList("list"); + list.insert(0, 123); + { + const doc2 = new Loro(); + let triggered = false; + doc2.subscribe((event) => { + expect(event.by).toBe("import"); + triggered = true; + }); + doc2.import(doc.exportFrom()); + await oneMs(); + expect(triggered).toBeTruthy(); + } + { + const doc2 = new Loro(); + let triggered = false; + doc2.subscribe((event) => { + expect(event.by).toBe("import"); + triggered = true; + }); + doc2.import(doc.exportSnapshot()); + await oneMs(); + expect(triggered).toBeTruthy(); + } + }); + + it("checkout event", async () => { + const doc = new Loro(); + const list = doc.getList("list"); + list.insert(0, 123); + doc.commit(); + let triggered = false; + doc.subscribe((e) => { + expect(e.by).toBe("checkout"); + triggered = true; + }); + + doc.checkout([]); + await oneMs(); + expect(triggered).toBeTruthy(); + }); }); function oneMs(): Promise { diff --git a/loro-js/tests/issue.test.ts b/loro-js/tests/issue.test.ts index e5a97a07..cd15ba4c 100644 --- a/loro-js/tests/issue.test.ts +++ b/loro-js/tests/issue.test.ts @@ -1,73 +1,69 @@ - import { describe, expect, expectTypeOf, it } from "vitest"; -import { - Loro, -} from "../src"; +import { Loro } from "../src"; import { Container, LoroText, OpId } from "../dist/loro"; import { setDebug } from "loro-wasm"; it("#211", () => { - const loro1 = new Loro() - loro1.setPeerId(0n) - const text1 = loro1.getText("text") + const loro1 = new Loro(); + loro1.setPeerId(0n); + const text1 = loro1.getText("text"); - const loro2 = new Loro() - loro2.setPeerId(1n) - const text2 = loro2.getText("text") + const loro2 = new Loro(); + loro2.setPeerId(1n); + const text2 = loro2.getText("text"); - console.log("[1] Insert T to #0") - text1.insert(0, 'T') - loro1.commit() - show(text1, loro1, text2, loro2) + console.log("[1] Insert T to #0"); + text1.insert(0, "T"); + loro1.commit(); + show(text1, loro1, text2, loro2); - console.log("[2] Synchronize") - loro1.import(loro2.exportFrom(loro1.version())) - loro2.import(loro1.exportFrom(loro2.version())) - show(text1, loro1, text2, loro2) - const frontiers1After2 = loro1.frontiers() - const frontiers2After2 = loro2.frontiers() + console.log("[2] Synchronize"); + loro1.import(loro2.exportFrom(loro1.version())); + loro2.import(loro1.exportFrom(loro2.version())); + show(text1, loro1, text2, loro2); + const frontiers1After2 = loro1.frontiers(); + const frontiers2After2 = loro2.frontiers(); - console.log("[3] Append A to #0") - text1.insert(1, 'A') - loro1.commit() - show(text1, loro1, text2, loro2) + console.log("[3] Append A to #0"); + text1.insert(1, "A"); + loro1.commit(); + show(text1, loro1, text2, loro2); - console.log("[4] Append B to #1") - text2.insert(1, 'B') - loro2.commit() - show(text1, loro1, text2, loro2) + console.log("[4] Append B to #1"); + text2.insert(1, "B"); + loro2.commit(); + show(text1, loro1, text2, loro2); - console.log("[5] Play back to the frontiers after 2") - loro1.checkout(frontiers1After2) - loro2.checkout(frontiers2After2) - show(text1, loro1, text2, loro2) + console.log("[5] Play back to the frontiers after 2"); + loro1.checkout(frontiers1After2); + loro2.checkout(frontiers2After2); + show(text1, loro1, text2, loro2); - console.log("[6] Check both to the latest") - loro1.checkoutToLatest() - loro2.checkoutToLatest() - show(text1, loro1, text2, loro2) - const frontiers1Before7 = loro1.frontiers() - const frontiers2Before7 = loro2.frontiers() + console.log("[6] Check both to the latest"); + loro1.checkoutToLatest(); + loro2.checkoutToLatest(); + show(text1, loro1, text2, loro2); + const frontiers1Before7 = loro1.frontiers(); + const frontiers2Before7 = loro2.frontiers(); - console.log("[7] Append B to #1") - text2.insert(2, 'B') - loro2.commit() - show(text1, loro1, text2, loro2) + console.log("[7] Append B to #1"); + text2.insert(2, "B"); + loro2.commit(); + show(text1, loro1, text2, loro2); - console.log("[8] Play back to the frontiers before 7") + console.log("[8] Play back to the frontiers before 7"); console.log("----------------------------------------------------------"); - loro1.checkout(frontiers1Before7) + loro1.checkout(frontiers1Before7); console.log("----------------------------------------------------------"); - loro2.checkout(frontiers2Before7) - show(text1, loro1, text2, loro2) -}) - + loro2.checkout(frontiers2Before7); + show(text1, loro1, text2, loro2); +}); function show(text1: LoroText, loro1: Loro, text2: LoroText, loro2: Loro) { - console.log(` #0 has content: ${JSON.stringify(text1.toString())}`) - console.log(` #0 has frontiers: ${showFrontiers(loro1.frontiers())}`) - console.log(` #1 has content: ${JSON.stringify(text2.toString())}`) - console.log(` #1 has frontiers: ${showFrontiers(loro2.frontiers())}`) + console.log(` #0 has content: ${JSON.stringify(text1.toString())}`); + console.log(` #0 has frontiers: ${showFrontiers(loro1.frontiers())}`); + console.log(` #1 has content: ${JSON.stringify(text2.toString())}`); + console.log(` #1 has frontiers: ${showFrontiers(loro2.frontiers())}`); } function showFrontiers(frontiers: OpId[]) { diff --git a/loro-js/tests/misc.test.ts b/loro-js/tests/misc.test.ts index d8abdccd..935221b5 100644 --- a/loro-js/tests/misc.test.ts +++ b/loro-js/tests/misc.test.ts @@ -129,15 +129,15 @@ describe("sync", () => { const b = new Loro(); let a_version: undefined | VersionVector = undefined; let b_version: undefined | VersionVector = undefined; - a.subscribe((e: { local: boolean }) => { - if (e.local) { + a.subscribe((e) => { + if (e.by == "local") { const exported = a.exportFrom(a_version); b.import(exported); a_version = a.version(); } }); - b.subscribe((e: { local: boolean }) => { - if (e.local) { + b.subscribe((e) => { + if (e.by == "local") { const exported = b.exportFrom(b_version); a.import(exported); b_version = b.version(); diff --git a/loro-js/tests/richtext.test.ts b/loro-js/tests/richtext.test.ts index 661fa806..2a16ec49 100644 --- a/loro-js/tests/richtext.test.ts +++ b/loro-js/tests/richtext.test.ts @@ -33,26 +33,33 @@ describe("richtext", () => { expect(text.toString()).toBe("👨‍👩‍👦a"); }); - it("emit event correctly", () => { + it("emit event correctly", async () => { const doc = new Loro(); const text = doc.getText("text"); - text.subscribe(doc, (event) => { - if (event.diff.type == "text") { - expect(event.diff.diff).toStrictEqual([ - { - insert: "Hello", - attributes: { - bold: true, + let triggered = false; + text.subscribe(doc, (e) => { + for (const event of e.events) { + if (event.diff.type == "text") { + expect(event.diff.diff).toStrictEqual([ + { + insert: "Hello", + attributes: { + bold: true, + }, }, - }, - { - insert: " World!", - }, - ] as Delta[]); + { + insert: " World!", + }, + ] as Delta[]); + triggered = true; + } } }); text.insert(0, "Hello World!"); text.mark({ start: 0, end: 5 }, "bold", true); + doc.commit(); + await new Promise((r) => setTimeout(r, 1)); + expect(triggered).toBeTruthy(); }); it("emit event from merging doc correctly", async () => { diff --git a/loro-js/tests/version.test.ts b/loro-js/tests/version.test.ts index ed5c2c3d..ccd354de 100644 --- a/loro-js/tests/version.test.ts +++ b/loro-js/tests/version.test.ts @@ -47,24 +47,39 @@ describe("Frontiers", () => { doc1.commit(); expect(() => { - doc1.cmpFrontiers([{ peer: "1", counter: 1 }], [{ - peer: "2", - counter: 10, - }]); + doc1.cmpFrontiers( + [{ peer: "1", counter: 1 }], + [ + { + peer: "2", + counter: 10, + }, + ], + ); }).toThrow(); expect(doc1.cmpFrontiers([], [{ peer: "1", counter: 1 }])).toBe(-1); expect(doc1.cmpFrontiers([], [])).toBe(0); expect( - doc1.cmpFrontiers([{ peer: "1", counter: 4 }], [{ - peer: "2", - counter: 3, - }]), + doc1.cmpFrontiers( + [{ peer: "1", counter: 4 }], + [ + { + peer: "2", + counter: 3, + }, + ], + ), ).toBe(-1); expect( - doc1.cmpFrontiers([{ peer: "1", counter: 5 }], [{ - peer: "2", - counter: 3, - }]), + doc1.cmpFrontiers( + [{ peer: "1", counter: 5 }], + [ + { + peer: "2", + counter: 3, + }, + ], + ), ).toBe(1); }); }); @@ -77,9 +92,7 @@ it("peer id repr should be consistent", () => { const f = doc.frontiers(); expect(f[0].peer).toBe(id); const child = new LoroMap(); - console.dir(child); const map = doc.getList("list").insertContainer(0, child); - console.dir(child); const mapId = map.id; const peerIdInContainerId = mapId.split(":")[1].split("@")[1]; expect(peerIdInContainerId).toBe(id); @@ -115,9 +128,9 @@ describe("Version", () => { const v = a.version(); const temp = a.vvToFrontiers(v); expect(temp).toStrictEqual(a.frontiers()); - expect(a.frontiers()).toStrictEqual( - [{ peer: "0", counter: 2 }] as OpId[], - ); + expect(a.frontiers()).toStrictEqual([ + { peer: "0", counter: 2 }, + ] as OpId[]); } }); @@ -156,7 +169,6 @@ it("get import blob metadata", () => { expect(meta.startTimestamp).toBe(0); expect(meta.endTimestamp).toBe(0); expect(meta.isSnapshot).toBeFalsy(); - console.log(meta.startFrontiers); expect(meta.startFrontiers.length).toBe(0); }