mirror of
https://github.com/loro-dev/loro.git
synced 2024-11-24 12:20:06 +00:00
feat: Add isDeleted() method to containers (#555)
* feat: Add isDeleted() method to containers - Add isDeleted() method to all container types (Text, Map, List, Tree, etc.) - Fix deletion tracking for containers in tree operations - Add tests to verify deletion state across different scenarios * chore: fix redundant field names --------- Co-authored-by: Leon Zhao <leeeon233@gmail.com>
This commit is contained in:
parent
55e0a4596e
commit
ee26952fc0
8 changed files with 162 additions and 4 deletions
5
.changeset/soft-goats-peel.md
Normal file
5
.changeset/soft-goats-peel.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"loro-crdt": patch
|
||||
---
|
||||
|
||||
Add isDeleted() method to each container
|
|
@ -1268,6 +1268,7 @@ impl ContainerState for TreeState {
|
|||
}
|
||||
|
||||
fn apply_local_op(&mut self, raw_op: &RawOp, _op: &Op) -> LoroResult<ApplyLocalOpReturn> {
|
||||
let mut deleted_containers = vec![];
|
||||
match &raw_op.content {
|
||||
crate::op::RawOpContent::Tree(tree) => match &**tree {
|
||||
TreeOp::Create {
|
||||
|
@ -1291,13 +1292,17 @@ impl ContainerState for TreeState {
|
|||
}
|
||||
TreeOp::Delete { target } => {
|
||||
let parent = TreeParentId::Deleted;
|
||||
deleted_containers.push(ContainerID::new_normal(
|
||||
target.id(),
|
||||
loro_common::ContainerType::Map,
|
||||
));
|
||||
self.mov(*target, parent, raw_op.id_full(), None, true)?;
|
||||
}
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
// self.check_tree_integrity();
|
||||
Ok(Default::default())
|
||||
Ok(ApplyLocalOpReturn { deleted_containers })
|
||||
}
|
||||
|
||||
fn to_diff(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import init, { initSync, LoroDoc } from "../web/loro_wasm.js";
|
||||
import init, { initSync, LoroDoc, LoroMap } from "../web/loro_wasm.js";
|
||||
import { expect } from "npm:expect";
|
||||
|
||||
await init();
|
||||
|
@ -28,3 +28,16 @@ Deno.test("fork when detached", () => {
|
|||
doc.checkoutToLatest();
|
||||
console.log(doc.getText("text").toString()); // "Hello, world! Alice!"
|
||||
});
|
||||
|
||||
Deno.test("isDeleted", () => {
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getList("list");
|
||||
expect(list.isDeleted()).toBe(false);
|
||||
const tree = doc.getTree("root");
|
||||
const node = tree.createNode(undefined, undefined);
|
||||
const containerBefore = node.data.setContainer("container", new LoroMap());
|
||||
containerBefore.set("A", "B");
|
||||
tree.delete(node.id);
|
||||
const containerAfter = doc.getContainerById(containerBefore.id) as LoroMap;
|
||||
expect(containerAfter.isDeleted()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -2273,6 +2273,11 @@ impl LoroText {
|
|||
.get_cursor(pos, Side::Middle)
|
||||
.map(|x| peer_id_to_js(x.id.unwrap().peer))
|
||||
}
|
||||
|
||||
/// Check if the container is deleted
|
||||
pub fn isDeleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoroText {
|
||||
|
@ -2614,6 +2619,11 @@ impl LoroMap {
|
|||
.get_last_editor(key)
|
||||
.map(|x| JsValue::from_str(&x.to_string()).into())
|
||||
}
|
||||
|
||||
/// Check if the container is deleted
|
||||
pub fn isDeleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoroMap {
|
||||
|
@ -2938,6 +2948,11 @@ impl LoroList {
|
|||
pub fn getIdAt(&self, pos: usize) -> Option<JsID> {
|
||||
self.handler.get_id_at(pos).map(|x| id_to_js(&x).into())
|
||||
}
|
||||
|
||||
/// Check if the container is deleted
|
||||
pub fn isDeleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoroList {
|
||||
|
@ -3315,6 +3330,11 @@ impl LoroMovableList {
|
|||
pub fn getLastEditorAt(&self, pos: usize) -> Option<JsStrPeerID> {
|
||||
self.handler.get_last_editor_at(pos).map(peer_id_to_js)
|
||||
}
|
||||
|
||||
/// Check if the container is deleted
|
||||
pub fn isDeleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
/// The handler of a tree(forest) container.
|
||||
|
@ -3991,6 +4011,11 @@ impl LoroTree {
|
|||
pub fn is_fractional_index_enabled(&self) -> bool {
|
||||
self.handler.is_fractional_index_enabled()
|
||||
}
|
||||
|
||||
/// Check if the container is deleted
|
||||
pub fn isDeleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoroTree {
|
||||
|
|
|
@ -700,3 +700,54 @@ it("update VV", () => {
|
|||
const map = vv.toJSON();
|
||||
expect(map).toStrictEqual(new Map([["1", 1], ["2", 2]]))
|
||||
})
|
||||
|
||||
describe("isDeleted", () => {
|
||||
it("test text container deletion", () => {
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getList("list");
|
||||
expect(list.isDeleted()).toBe(false);
|
||||
const tree = doc.getTree("root");
|
||||
const node = tree.createNode();
|
||||
const containerBefore = node.data.setContainer("container", new LoroMap());
|
||||
containerBefore.set("A", "B");
|
||||
tree.delete(node.id);
|
||||
const containerAfter = node.data;
|
||||
expect(containerAfter.isDeleted()).toBe(true);
|
||||
})
|
||||
|
||||
it("movable list setContainer", () => {
|
||||
const doc = new LoroDoc();
|
||||
const list = doc.getMovableList("list1");
|
||||
const map = list.insertContainer(0, new LoroMap());
|
||||
expect(map.isDeleted()).toBe(false);
|
||||
list.set(0, 1);
|
||||
expect(map.isDeleted()).toBe(true);
|
||||
})
|
||||
|
||||
it("map set", () => {
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
const sub = map.setContainer("sub", new LoroMap());
|
||||
expect(sub.isDeleted()).toBe(false);
|
||||
map.set("sub", "value");
|
||||
expect(sub.isDeleted()).toBe(true);
|
||||
})
|
||||
|
||||
it("remote map set", () => {
|
||||
const doc = new LoroDoc();
|
||||
const map = doc.getMap("map");
|
||||
const sub = map.setContainer("sub", new LoroMap());
|
||||
|
||||
const docB = new LoroDoc();
|
||||
docB.import(doc.export({ mode: "snapshot" }));
|
||||
const subB = docB.getByPath("map/sub") as LoroMap;
|
||||
expect(sub.isDeleted()).toBe(false);
|
||||
expect(subB.isDeleted()).toBe(false);
|
||||
|
||||
map.set("sub", "value");
|
||||
docB.import(doc.export({ mode: "snapshot" }));
|
||||
|
||||
expect(sub.isDeleted()).toBe(true);
|
||||
expect(subB.isDeleted()).toBe(true);
|
||||
})
|
||||
})
|
||||
|
|
|
@ -77,4 +77,8 @@ impl ContainerTrait for LoroCounter {
|
|||
fn try_from_container(container: Container) -> Option<Self> {
|
||||
container.into_counter().ok()
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -892,6 +892,8 @@ pub trait ContainerTrait: SealedTrait {
|
|||
fn get_attached(&self) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
/// Whether the container is deleted.
|
||||
fn is_deleted(&self) -> bool;
|
||||
}
|
||||
|
||||
/// LoroList container. It's used to model array.
|
||||
|
@ -943,6 +945,10 @@ impl ContainerTrait for LoroList {
|
|||
fn try_from_container(container: Container) -> Option<Self> {
|
||||
container.into_list().ok()
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
impl LoroList {
|
||||
|
@ -1213,6 +1219,10 @@ impl ContainerTrait for LoroMap {
|
|||
fn try_from_container(container: Container) -> Option<Self> {
|
||||
container.into_map().ok()
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
impl LoroMap {
|
||||
|
@ -1380,6 +1390,10 @@ impl ContainerTrait for LoroText {
|
|||
fn try_from_container(container: Container) -> Option<Self> {
|
||||
container.into_text().ok()
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
impl LoroText {
|
||||
|
@ -1686,6 +1700,10 @@ impl ContainerTrait for LoroTree {
|
|||
fn try_from_container(container: Container) -> Option<Self> {
|
||||
container.into_tree().ok()
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
/// A tree node in the [LoroTree].
|
||||
|
@ -2087,6 +2105,10 @@ impl ContainerTrait for LoroMovableList {
|
|||
{
|
||||
self.handler.get_attached().map(Self::from_handler)
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
impl LoroMovableList {
|
||||
|
@ -2369,6 +2391,10 @@ impl ContainerTrait for LoroUnknown {
|
|||
{
|
||||
self.handler.get_attached().map(Self::from_handler)
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
self.handler.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
use enum_as_inner::EnumAsInner;
|
||||
|
@ -2459,6 +2485,19 @@ impl ContainerTrait for Container {
|
|||
{
|
||||
Some(container)
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
match self {
|
||||
Container::List(x) => x.is_deleted(),
|
||||
Container::Map(x) => x.is_deleted(),
|
||||
Container::Text(x) => x.is_deleted(),
|
||||
Container::Tree(x) => x.is_deleted(),
|
||||
Container::MovableList(x) => x.is_deleted(),
|
||||
#[cfg(feature = "counter")]
|
||||
Container::Counter(x) => x.is_deleted(),
|
||||
Container::Unknown(x) => x.is_deleted(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Container {
|
||||
|
|
|
@ -9,8 +9,9 @@ use std::{
|
|||
};
|
||||
|
||||
use loro::{
|
||||
awareness::Awareness, loro_value, CommitOptions, ContainerID, ContainerType, ExportMode,
|
||||
Frontiers, FrontiersNotIncluded, LoroDoc, LoroError, LoroList, LoroMap, LoroText, ToJson,
|
||||
awareness::Awareness, loro_value, CommitOptions, ContainerID, ContainerTrait, ContainerType,
|
||||
ExportMode, Frontiers, FrontiersNotIncluded, LoroDoc, LoroError, LoroList, LoroMap, LoroText,
|
||||
ToJson,
|
||||
};
|
||||
use loro_internal::{encoding::EncodedBlobMode, handler::TextDelta, id::ID, vv, LoroResult};
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
@ -2222,3 +2223,18 @@ fn get_changed_containers_in() {
|
|||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_deleted() {
|
||||
let doc = LoroDoc::new();
|
||||
let list = doc.get_list("list");
|
||||
assert!(!list.is_deleted());
|
||||
let tree = doc.get_tree("root");
|
||||
let node = tree.create(None).unwrap();
|
||||
let map = tree.get_meta(node).unwrap();
|
||||
let container_before = map.insert_container("container", LoroMap::new()).unwrap();
|
||||
container_before.insert("A", "B").unwrap();
|
||||
tree.delete(node).unwrap();
|
||||
let container_after = doc.get_map(&container_before.id());
|
||||
assert!(container_after.is_deleted());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue