diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index f33c7d4c..e9768b39 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -1246,7 +1246,76 @@ impl LoroMap { self.0.get(key).into() } - /// Get the keys and values without being resolved recursively. + /// Get the keys of the map. + /// + /// @example + /// ```ts + /// import { Loro } from "loro-crdt"; + /// + /// const doc = new Loro(); + /// const map = doc.getMap("map"); + /// map.set("foo", "bar"); + /// map.set("baz", "bar"); + /// const keys = map.keys(); // ["foo", "baz"] + /// ``` + pub fn keys(&self) -> Vec { + let mut ans = Vec::with_capacity(self.0.len()); + self.0.for_each(|k, v| { + if v.value.is_some() { + ans.push(k.to_string().into()); + } + }); + ans + } + + /// Get the values of the map. + /// + /// @example + /// ```ts + /// import { Loro } from "loro-crdt"; + /// + /// const doc = new Loro(); + /// const map = doc.getMap("map"); + /// map.set("foo", "bar"); + /// map.set("baz", "bar"); + /// const values = map.values(); // ["bar", "bar"] + /// ``` + pub fn values(&self) -> Vec { + let mut ans: Vec = Vec::with_capacity(self.0.len()); + self.0.for_each(|_, v| { + if let Some(v) = &v.value { + ans.push(v.clone().into()); + } + }); + ans + } + + /// Get the entries of the map. + /// + /// @example + /// ```ts + /// import { Loro } from "loro-crdt"; + /// + /// const doc = new Loro(); + /// const map = doc.getMap("map"); + /// map.set("foo", "bar"); + /// map.set("baz", "bar"); + /// const entries = map.entries(); // [["foo", "bar"], ["baz", "bar"]] + /// ``` + pub fn entries(&self) -> Vec { + let mut ans: Vec = Vec::with_capacity(self.0.len()); + self.0.for_each(|k, v| { + if let Some(v) = &v.value { + let array = Array::new(); + array.push(&k.to_string().into()); + array.push(&v.clone().into()); + ans.push(array.into()); + } + }); + ans + } + + /// Get the keys and values shallowly /// /// {@link LoroMap.getDeepValue} /// diff --git a/loro-js/tests/basic.test.ts b/loro-js/tests/basic.test.ts index bb99c7d8..07660c3e 100644 --- a/loro-js/tests/basic.test.ts +++ b/loro-js/tests/basic.test.ts @@ -1,11 +1,5 @@ import { describe, expect, it } from "vitest"; -import { - ContainerID, - Loro, - LoroList, - LoroMap, - setPanicHook, -} from "../src"; +import { ContainerID, Loro, LoroList, LoroMap, setPanicHook } from "../src"; setPanicHook(); @@ -21,25 +15,25 @@ it("basic example", () => { map.set("key", "value"); expect(doc.toJson()).toStrictEqual({ list: ["A", "B", "C"], - map: { key: "value" } + map: { key: "value" }, }); // delete 2 elements at index 0 - list.delete(0, 2) + list.delete(0, 2); expect(doc.toJson()).toStrictEqual({ list: ["C"], - map: { key: "value" } + map: { key: "value" }, }); // Insert a text container to the list const text = list.insertContainer(0, "Text"); text.insert(0, "Hello"); - text.insert(0, "Hi! ") + text.insert(0, "Hi! "); // delete 1 element at index 0 expect(doc.toJson()).toStrictEqual({ list: ["Hi! Hello", "C"], - map: { key: "value" } + map: { key: "value" }, }); // Insert a list container to the map @@ -47,9 +41,9 @@ it("basic example", () => { list2.insert(0, 1); expect(doc.toJson()).toStrictEqual({ list: ["Hi! Hello", "C"], - map: { key: "value", test: [1] } + map: { key: "value", test: [1] }, }); -}) +}); it("basic sync example", () => { const docA = new Loro(); @@ -61,28 +55,26 @@ it("basic sync example", () => { // B import the ops from A docB.import(docA.exportFrom()); expect(docB.toJson()).toStrictEqual({ - list: ["A", "B", "C"] - }) + list: ["A", "B", "C"], + }); const listB: LoroList = docB.getList("list"); // delete 1 element at index 1 listB.delete(1, 1); // A import the ops from B - docA.import(docB.exportFrom(docA.version())) + docA.import(docB.exportFrom(docA.version())); // list at A is now ["A", "C"], with the same state as B expect(docA.toJson()).toStrictEqual({ - list: ["A", "C"] + list: ["A", "C"], }); expect(docA.toJson()).toStrictEqual(docB.toJson()); -}) +}); it("basic events", () => { const doc = new Loro(); - doc.subscribe(event => { - - }); + doc.subscribe((event) => {}); const list = doc.getList("list"); -}) +}); describe("list", () => { it("insert containers", () => { @@ -95,13 +87,13 @@ describe("list", () => { expect(typeof v).toBe("string"); const m = doc.getMap(v as ContainerID); expect(m.getDeepValue()).toStrictEqual({ key: "value" }); - }) + }); it.todo("iterate"); -}) +}); describe("import", () => { - it('pending', () => { + it("pending", () => { const a = new Loro(); a.getText("text").insert(0, "a"); const b = new Loro(); @@ -111,13 +103,47 @@ describe("import", () => { c.import(b.exportFrom()); c.getText("text").insert(2, "c"); - // c export from b's version, which cannot be imported directly to a. + // c export from b's version, which cannot be imported directly to a. // This operation is pending. - a.import(c.exportFrom(b.version())) + a.import(c.exportFrom(b.version())); expect(a.getText("text").toString()).toBe("a"); // a import the missing ops from b. It makes the pending operation from c valid. - a.import(b.exportFrom(a.version())) + a.import(b.exportFrom(a.version())); expect(a.getText("text").toString()).toBe("abc"); - }) -}) + }); +}); + +describe("map", () => { + it("keys", () => { + const doc = new Loro(); + const map = doc.getMap("map"); + map.set("foo", "bar"); + map.set("baz", "bar"); + const entries = map.keys(); + expect(entries).toStrictEqual(["foo", "baz"]); + }); + + it("values", () => { + const doc = new Loro(); + const map = doc.getMap("map"); + map.set("foo", "bar"); + map.set("baz", "bar"); + const entries = map.values(); + expect(entries).toStrictEqual(["bar", "bar"]); + }); + + it("entries", () => { + const doc = new Loro(); + const map = doc.getMap("map"); + map.set("foo", "bar"); + map.set("baz", "bar"); + map.set("new", 11); + map.delete("new"); + const entries = map.entries(); + expect(entries).toStrictEqual([ + ["foo", "bar"], + ["baz", "bar"], + ]); + }); +});