Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,7 @@ When running scripts, use `npx turbo`, not `npm`.

Run e2e tests headlessly using Playwright:
npx turbo build && env HEADLESS=1 playwright test --retries=5 --

# Documentation

All documentation lives in the `docs/` directory, as Markdown files.
17 changes: 13 additions & 4 deletions docs/pages/api-reference/liveblocks-client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4480,17 +4480,26 @@ list.toImmutable();

### delete [#LiveList.delete]

Deletes an element at the specified index. If the index doesn鈥檛 exist, an
`Error` is thrown.
Deletes the element living at the specified index locally. If the index doesn't
exist, an `Error` is thrown.

```ts
const list = new LiveList(["adrien", "jonathan"]);
list.delete(1);
list.delete(0);

// ["adrien"]
// ["jonathan"]
list.toImmutable();
```

This operation uses ID-based semantics, not position-based. When called, it
reads the item at the specified index from the local state, then sends a "delete
item with ID X" instruction to the server.

If clients A and B both see a LiveList containing `["foo", "bar"]`, and client A
calls `.insert("qux", 0)`, while client B simultaneously calls `.delete(0)`, the
end result will always be `["qux", "bar"]` on both clients, and never
`["foo", "bar"]`.

<PropertiesListEmpty title="Returns">_Nothing_</PropertiesListEmpty>

<PropertiesList title="Arguments">
Expand Down
9 changes: 8 additions & 1 deletion e2e/next-sandbox/pages/storage/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ const {
RoomProvider,
useCanRedo,
useCanUndo,
useStorage,
useMutation,
useRedo,
useStorage,
useSyncStatus,
useUndo,
} = createRoomContext<never, { map: LiveMap<string, string> }>(client);

Expand All @@ -42,6 +43,7 @@ function Sandbox() {
const redo = useRedo();
const canUndo = useCanUndo();
const canRedo = useCanRedo();
const syncStatus = useSyncStatus();
const map = useStorage((root) => root.map);

const set_ = useMutation(({ storage }, key: string, value: string) => {
Expand Down Expand Up @@ -113,6 +115,11 @@ function Sandbox() {
<table style={styles.dataTable}>
<tbody>
<Row id="renderCount" name="Render count" value={renderCount} />
<Row
id="syncStatus"
name="Sync status (immediate)"
value={syncStatus}
/>
<Row id="mapSize" name="Map size" value={map.size} />
<Row id="map" name="Serialized" value={Object.fromEntries(map)} />
</tbody>
Expand Down
7 changes: 7 additions & 0 deletions e2e/next-sandbox/pages/storage/object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const {
useRedo,
useSelf,
useStorage,
useSyncStatus,
useUndo,
} = createRoomContext<
never,
Expand Down Expand Up @@ -57,6 +58,7 @@ function Sandbox() {
const canRedo = useCanRedo();
const obj = useStorage((root) => root.object);
const me = useSelf();
const syncStatus = useSyncStatus();

const set_ = useMutation(
({ storage }, key: string, value: number | LiveObject<{ a: number }>) => {
Expand Down Expand Up @@ -145,6 +147,11 @@ function Sandbox() {
<table style={styles.dataTable}>
<tbody>
<Row id="renderCount" name="Render count" value={renderCount} />
<Row
id="syncStatus"
name="Sync status (immediate)"
value={syncStatus}
/>
<Row id="obj" name="Serialized" value={lsonToJson(obj)} />
</tbody>
</table>
Expand Down
5 changes: 5 additions & 0 deletions e2e/next-sandbox/pages/zustand.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createLiveblocksContext } from "@liveblocks/react";
import type { WithLiveblocks } from "@liveblocks/zustand";
import { liveblocks } from "@liveblocks/zustand";
import { useEffect } from "react";
Expand All @@ -16,6 +17,8 @@ import { createLiveblocksClient } from "../utils/createClient";

const client = createLiveblocksClient();

const { useSyncStatus } = createLiveblocksContext(client);

type State = {
// Presence
name: string;
Expand Down Expand Up @@ -69,6 +72,7 @@ export default function ZustandApp() {
clear,
liveblocks: { enterRoom, leaveRoom, isStorageLoading, room, others },
} = useStore();
const syncStatus = useSyncStatus();

const connectionId = room?.getSelf()?.connectionId ?? 0;

Expand Down Expand Up @@ -167,6 +171,7 @@ export default function ZustandApp() {
<table style={styles.dataTable}>
<tbody>
<Row id="renderCount" name="Render count" value={renderCount} />
<Row id="syncStatus" name="Sync status" value={syncStatus} />
<Row id="connectionId" name="Connection ID" value={connectionId} />
</tbody>
</table>
Expand Down
28 changes: 10 additions & 18 deletions e2e/next-sandbox/test/storage/list-with-suspense.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ test.describe("Storage w/ Suspense", () => {
await waitUntilEqualOnAllPages(pages, "#items");

await page1.click("#push");

await waitForJson(pages, "#syncStatus", "synchronized");
await waitForJson(pages, "#numItems", 3);
await waitUntilEqualOnAllPages(pages, "#items");
});

// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip("list move", async () => {
test("list move", async () => {
const [page1] = pages;
await page1.click("#clear");
await waitForJson(pages, "#numItems", 0);
Expand All @@ -65,12 +66,12 @@ test.describe("Storage w/ Suspense", () => {
await page1.click("#move");
}

await waitForJson(pages, "#syncStatus", "synchronized");
await expectJson(page1, "#numItems", 5);
await waitUntilEqualOnAllPages(pages, "#items");
});

// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip("push conflicts", async () => {
test("push conflicts", async () => {
const [page1, page2] = pages;
await page1.click("#clear");
await waitForJson(pages, "#numItems", 0);
Expand All @@ -80,13 +81,12 @@ test.describe("Storage w/ Suspense", () => {
await page2.click("#push");
}

// await expectJson(pages, "#numItems", n => n >= 10 && n <= 20);
await waitForJson(pages, "#syncStatus", "synchronized");
await waitForJson(pages, "#numItems", 20);
await waitUntilEqualOnAllPages(pages, "#items");
});

// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip("set conflicts", async () => {
test("set conflicts", async () => {
const [page1, page2] = pages;
await page1.click("#clear");
await page1.click("#push");
Expand All @@ -96,20 +96,15 @@ test.describe("Storage w/ Suspense", () => {
// no await to create randomness
await page1.click("#set");
await page2.click("#set");

// In this test, we should never see a list of less than or more than
// 1 element. When this happens, we'll want to immediately fail here.
await expectJson(page1, "#numItems", 1);
await expectJson(page2, "#numItems", 1);
}

await waitForJson(pages, "#syncStatus", "synchronized");
await expectJson(page1, "#numItems", 1);
await expectJson(page2, "#numItems", 1);
await waitUntilEqualOnAllPages(pages, "#items");
});

// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip("fuzzy with undo/redo push delete and move", async () => {
test("fuzzy with undo/redo push delete and move", async () => {
const [page1] = pages;
await page1.click("#clear");
await waitForJson(pages, "#numItems", 0);
Expand Down Expand Up @@ -138,14 +133,11 @@ test.describe("Storage w/ Suspense", () => {
} else {
await page.click(pickFrom(actions), { force: true });
}

// In this test, we should never see a list of more than 1 element. When
// it happens, we'll want to immediately fail here.
await expectJson(page, "#numItems", 1);
}
await nanoSleep();
}

await waitForJson(pages, "#syncStatus", "synchronized");
await waitUntilEqualOnAllPages(pages, "#items");
});
});
17 changes: 4 additions & 13 deletions e2e/next-sandbox/test/storage/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ test.describe("Storage - LiveList", () => {
await waitUntilEqualOnAllPages(pages, "#items");
});

// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip("set conflicts", async () => {
test("set conflicts", async () => {
const [page1, page2] = pages;
await page1.click("#clear");
await page1.click("#push");
Expand All @@ -94,20 +93,15 @@ test.describe("Storage - LiveList", () => {
for (let i = 0; i < 30; i++) {
await page1.click("#set");
await page2.click("#set");

// In this test, we should never see a list of less than or more than
// 1 element. When this happens, we'll want to immediately fail here.
await expectJson(page1, "#numItems", 1);
await expectJson(page2, "#numItems", 1);
}

await waitForJson(pages, "#syncStatus", "synchronized");
await expectJson(page1, "#numItems", 1);
await expectJson(page2, "#numItems", 1);
await waitUntilEqualOnAllPages(pages, "#items");
});

// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip("fuzzy with undo/redo push delete and move", async () => {
test("fuzzy with undo/redo push delete and move", async () => {
const [page1] = pages;
await page1.click("#clear");
await waitForJson(pages, "#numItems", 0);
Expand Down Expand Up @@ -136,14 +130,11 @@ test.describe("Storage - LiveList", () => {
} else {
await page.click(pickFrom(actions), { force: true });
}

// In this test, we should never see a list of more than 1 element. When
// it happens, we'll want to immediately fail here.
await expectJson(page, "#numItems", 1);
}
await nanoSleep();
}

await waitForJson(pages, "#syncStatus", "synchronized");
await waitUntilEqualOnAllPages(pages, "#items");
});
});
6 changes: 3 additions & 3 deletions e2e/next-sandbox/test/storage/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ test.describe("Storage - LiveMap", () => {
])
);

// TODO Definitely a bug here!
test.skip("fuzzy full w/ undo/redo", async () => {
test("fuzzy full w/ undo/redo", async () => {
const [page1] = pages;
await page1.click("#clear");

Expand All @@ -97,11 +96,12 @@ test.describe("Storage - LiveMap", () => {
await nanoSleep();
}

// TODO Investigate: sometimes these don't converge to the same value
await waitForJson(pages, "#syncStatus", "synchronized");
await waitUntilEqualOnAllPages(pages, "#map");

// Clean up the room after the test
await page1.click("#clear");
await waitForJson(pages, "#syncStatus", "synchronized");
await waitForJson(pages, "#map", {});
});
});
20 changes: 7 additions & 13 deletions e2e/next-sandbox/test/zustand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,30 +132,24 @@ test.describe("Zustand", () => {
await nanoSleep();
}

await waitForJson(pages, "#syncStatus", "synchronized");
await waitUntilEqualOnAllPages(pages, "#items");

await page1.click("#clear");
await waitForJson(pages, "#syncStatus", "synchronized");
await waitForJson(pages, "#numItems", 0);
};
}

test("fuzzy [push]", fuzzyTest(["#push"]));
test("fuzzy [push, delete]", fuzzyTest(["#push", "#delete"]));

// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip("fuzzy [push, undo]", fuzzyTest(["#push", "#undo"]));
// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip("fuzzy [delete, undo]", fuzzyTest(["#delete", "#undo"]));
// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip("fuzzy [push, undo, redo]", fuzzyTest(["#push", "#undo", "#redo"]));
// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip(
"fuzzy [delete, undo, redo]",
fuzzyTest(["#delete", "#undo", "#redo"])
);
test("fuzzy [push, undo]", fuzzyTest(["#push", "#undo"]));
test("fuzzy [delete, undo]", fuzzyTest(["#delete", "#undo"]));
test("fuzzy [push, undo, redo]", fuzzyTest(["#push", "#undo", "#redo"]));
test("fuzzy [delete, undo, redo]", fuzzyTest(["#delete", "#undo", "#redo"]));

// TODO FIXME Actually fails sometimes, there definitely is a bug here
test.skip(
test(
"fuzzy [push, delete, undo, redo]",
fuzzyTest(["#push", "#delete", "#undo", "#redo"])
);
Expand Down
Loading
Loading