From 20d920946e8e4a157ac0239ad55455d7fd3f8892 Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Wed, 15 Apr 2026 12:10:32 +0200 Subject: [PATCH 1/7] Fix `history.disable()` edge cases by defining weirdness out of existence (#3354) --- .../src/__tests__/room.devserver.test.ts | 263 ++++++++++++++++-- .../__tests__/LiveList.mockserver.test.ts | 12 +- .../__tests__/LiveObject.devserver.test.ts | 16 +- packages/liveblocks-core/src/room.ts | 32 ++- .../liveblocks-core/test-d/ToJson.test-d.ts | 1 - 5 files changed, 267 insertions(+), 57 deletions(-) diff --git a/packages/liveblocks-core/src/__tests__/room.devserver.test.ts b/packages/liveblocks-core/src/__tests__/room.devserver.test.ts index 76740beea71..24c02cb6958 100644 --- a/packages/liveblocks-core/src/__tests__/room.devserver.test.ts +++ b/packages/liveblocks-core/src/__tests__/room.devserver.test.ts @@ -5,7 +5,7 @@ * For connection state machine, auth, reconnection, and wire protocol tests, * see room.mockserver.test.ts. */ -import { describe, expect, onTestFinished, test } from "vitest"; +import { describe, expect, onTestFinished, test, vi } from "vitest"; import { LiveList } from "../crdts/LiveList"; import { LiveObject } from "../crdts/LiveObject"; @@ -101,9 +101,7 @@ describe("room (dev server)", () => { }); test("canUndo / canRedo", async () => { - const { room, root } = await prepareIsolatedStorageTest<{ - a: number; - }>({ + const { room, root } = await prepareIsolatedStorageTest<{ a: number }>({ liveblocksType: "LiveObject", data: { a: 1 }, }); @@ -121,9 +119,7 @@ describe("room (dev server)", () => { }); test("clearing undo/redo stack", async () => { - const { room, root } = await prepareIsolatedStorageTest<{ - a: number; - }>({ + const { room, root } = await prepareIsolatedStorageTest<{ a: number }>({ liveblocksType: "LiveObject", data: { a: 1 }, }); @@ -258,10 +254,8 @@ describe("room (dev server)", () => { }); }); - test("history.disable prevents mutations from appearing in undo stack", async () => { - const { room, root } = await prepareIsolatedStorageTest<{ - x: number; - }>({ + test("history.disable() prevents mutations from appearing in undo stack", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ liveblocksType: "LiveObject", data: { x: 0 }, }); @@ -274,10 +268,8 @@ describe("room (dev server)", () => { expect(room.history.canUndo()).toBe(false); }); - test("history.disable returns the callback's return value", async () => { - const { room } = await prepareIsolatedStorageTest<{ - x: number; - }>({ + test("history.disable() returns the callback's return value", async () => { + const { room } = await prepareIsolatedStorageTest<{ x: number }>({ liveblocksType: "LiveObject", data: { x: 0 }, }); @@ -287,10 +279,8 @@ describe("room (dev server)", () => { expect(result).toBe(42); }); - test("history.disable restores undo stack even if callback throws", async () => { - const { room, root } = await prepareIsolatedStorageTest<{ - x: number; - }>({ + test("history.disable() restores undo stack even if callback throws", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ liveblocksType: "LiveObject", data: { x: 0 }, }); @@ -314,7 +304,7 @@ describe("room (dev server)", () => { expect(room.history.canUndo()).toBe(false); }); - test("background write via history.disable does not interfere with user's undo history", async () => { + test("background write via history.disable() does not interfere with user's undo history", async () => { const { room, root } = await prepareIsolatedStorageTest<{ userText: string; generatedSummary: string; @@ -350,7 +340,7 @@ describe("room (dev server)", () => { expect(room.history.canUndo()).toBe(false); }); - test("disable must wrap batch, not the other way around", async () => { + test("disable() must wrap batch(), not the other way around", async () => { const { room, root } = await prepareIsolatedStorageTest<{ x: number; y: number; @@ -386,7 +376,7 @@ describe("room (dev server)", () => { expect(room.history.canUndo()).toBe(false); }); - test("nested history.disable calls work correctly", async () => { + test("nested history.disable() calls work correctly", async () => { const { room, root } = await prepareIsolatedStorageTest<{ x: number; y: number; @@ -407,10 +397,8 @@ describe("room (dev server)", () => { expect(room.history.canUndo()).toBe(false); }); - test("history.disable preserves the redo stack", async () => { - const { room, root } = await prepareIsolatedStorageTest<{ - x: number; - }>({ + test("history.disable() preserves the redo stack", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ liveblocksType: "LiveObject", data: { x: 0 }, }); @@ -429,4 +417,227 @@ describe("room (dev server)", () => { expect(root.get("x")).toBe(99); expect(room.history.canRedo()).toBe(true); }); + + test("history.clear() inside history.disable() preserves original history", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ + liveblocksType: "LiveObject", + data: { x: 0 }, + }); + + // Build up some undo/redo state + root.set("x", 1); + root.set("x", 2); + room.history.undo(); + expect(root.get("x")).toBe(1); + expect(room.history.canUndo()).toBe(true); + expect(room.history.canRedo()).toBe(true); + + // Clear inside disable only affects the temporary stacks, not the real ones + room.history.disable(() => { + // Inside disable, the real stacks are swapped out — starts empty + expect(room.history.canUndo()).toBe(false); + expect(room.history.canRedo()).toBe(false); + + // Adding an entry makes canUndo true within the block + root.set("x", 99); + expect(room.history.canUndo()).toBe(true); + + // Clear wipes the temporary stacks + room.history.clear(); + expect(room.history.canUndo()).toBe(false); + expect(room.history.canRedo()).toBe(false); + }); + + // The mutation inside disable() still applies to storage + expect(root.get("x")).toBe(99); + // Original history is preserved — clear() only wiped the temporary stacks + expect(room.history.canUndo()).toBe(true); + expect(room.history.canRedo()).toBe(true); + }); + + test("undo() inside history.disable() does not affect original undo stack", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ + liveblocksType: "LiveObject", + data: { x: 0 }, + }); + + // Build up 3 undo entries + root.set("x", 1); + root.set("x", 2); + root.set("x", 3); + + // Calling undo() inside disable operates on the empty temp stack — no-ops + room.history.disable(() => { + room.history.undo(); + room.history.undo(); + }); + + // All 3 original entries should still be intact + expect(root.get("x")).toBe(3); + for (let i = 3; i >= 1; i--) { + expect(room.history.canUndo()).toBe(true); + room.history.undo(); + expect(root.get("x")).toBe(i - 1); + } + expect(room.history.canUndo()).toBe(false); + }); + + test("undo() within history.disable() can revert block-local mutations", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ + liveblocksType: "LiveObject", + data: { x: 0 }, + }); + + // Build up 3 undo entries + root.set("x", 1); + root.set("x", 2); + root.set("x", 3); + + room.history.disable(() => { + root.set("x", 4); // Add 4th item (on temp stack) + room.history.undo(); // Undoes 4th item, back at 3 + root.set("x", 5); // Add new item + root.set("x", 6); // Add another + }); + + // Mutations applied, but undo stack restored to original 3 entries + expect(root.get("x")).toBe(6); + room.history.undo(); + expect(root.get("x")).toBe(2); + room.history.undo(); + expect(root.get("x")).toBe(1); + room.history.undo(); + expect(root.get("x")).toBe(0); + expect(room.history.canUndo()).toBe(false); + }); + + test("undo() after history.disable() undoes the last pre-disable mutation", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ + liveblocksType: "LiveObject", + data: { x: 0 }, + }); + + // Build up 3 undo entries + root.set("x", 1); + root.set("x", 2); + root.set("x", 3); + + room.history.disable(() => { + root.set("x", 99); // Add another + }); + + // Mutations applied, but undo stack restored to original 3 entries + expect(root.get("x")).toBe(99); + room.history.undo(); + expect(root.get("x")).toBe(2); + room.history.redo(); + expect(root.get("x")).toBe(99); + }); + + test("undo() inside history.disable() cannot go beyond the block start", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ + liveblocksType: "LiveObject", + data: { x: 0 }, + }); + + // Build up 3 undo entries + root.set("x", 1); + root.set("x", 2); + root.set("x", 3); + + room.history.disable(() => { + root.set("x", 4); // Add 4th item (on temp stack) + room.history.undo(); // Undoes 4th item, back at 3 + room.history.undo(); // No-op — temp stack is empty + }); + + // Original 3 entries intact — the second undo was a no-op + expect(root.get("x")).toBe(3); + room.history.undo(); + expect(root.get("x")).toBe(2); + room.history.undo(); + expect(root.get("x")).toBe(1); + room.history.undo(); + expect(root.get("x")).toBe(0); + expect(room.history.canUndo()).toBe(false); + }); + + test("undo() beyond block start and back again inside history.disable()", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ + liveblocksType: "LiveObject", + data: { x: 0 }, + }); + + // Build up 3 undo entries + root.set("x", 1); + root.set("x", 2); + root.set("x", 3); + + room.history.disable(() => { + root.set("x", 4); // Add 4th item (on temp stack) + room.history.undo(); // Undoes 4th item, back at 3 + room.history.undo(); // No-op — temp stack empty + room.history.undo(); // No-op — temp stack empty + root.set("x", 5); // Add new item + }); + + // Mutation applied, original 3 entries intact + expect(root.get("x")).toBe(5); + room.history.undo(); + expect(root.get("x")).toBe(2); + room.history.undo(); + expect(root.get("x")).toBe(1); + room.history.undo(); + expect(root.get("x")).toBe(0); + expect(room.history.canUndo()).toBe(false); + }); + + test("history.disable() at undo stack cap (50) does not evict oldest entry", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ + liveblocksType: "LiveObject", + data: { x: 0 }, + }); + + // Fill the undo stack to the cap (50 entries) + for (let i = 1; i <= 50; i++) { + root.set("x", i); + } + expect(root.get("x")).toBe(50); + + // Mutate inside disable — should not shift the real undo stack + room.history.disable(() => { + root.set("x", 999); + }); + + expect(root.get("x")).toBe(999); + + // All 50 original undo entries should still be intact + for (let i = 50; i >= 1; i--) { + expect(room.history.canUndo()).toBe(true); + room.history.undo(); + expect(root.get("x")).toBe(i - 1); + } + expect(room.history.canUndo()).toBe(false); + }); + + test("history.disable() never fires history subscription events", async () => { + const { room, root } = await prepareIsolatedStorageTest<{ x: number }>({ + liveblocksType: "LiveObject", + data: { x: 0 }, + }); + + // Build up undo state so canUndo is true + root.set("x", 1); + + const callback = vi.fn(); + onTestFinished(room.events.history.subscribe(callback)); + + // Mutations inside disable should not produce any history notifications + room.history.disable(() => { + root.set("x", 2); + root.set("x", 3); + }); + + expect(callback).not.toHaveBeenCalled(); + }); }); diff --git a/packages/liveblocks-core/src/crdts/__tests__/LiveList.mockserver.test.ts b/packages/liveblocks-core/src/crdts/__tests__/LiveList.mockserver.test.ts index 7f5ba892e9e..21657019233 100644 --- a/packages/liveblocks-core/src/crdts/__tests__/LiveList.mockserver.test.ts +++ b/packages/liveblocks-core/src/crdts/__tests__/LiveList.mockserver.test.ts @@ -481,9 +481,7 @@ describe("LiveList edge cases", () => { describe("reconnect with remote changes and subscribe", () => { test("register added to list", async () => { const { expectStorage, room, root, wss } = - await prepareIsolatedStorageTest<{ - items: LiveList; - }>( + await prepareIsolatedStorageTest<{ items: LiveList }>( [ createSerializedRoot(), createSerializedList("0:1", "root", "items"), @@ -563,9 +561,7 @@ describe("LiveList edge cases", () => { test("register moved in list", async () => { const { expectStorage, room, root, wss } = - await prepareIsolatedStorageTest<{ - items: LiveList; - }>( + await prepareIsolatedStorageTest<{ items: LiveList }>( [ createSerializedRoot(), createSerializedList("0:1", "root", "items"), @@ -634,9 +630,7 @@ describe("LiveList edge cases", () => { test("register deleted from list", async () => { const { expectStorage, room, root, wss } = - await prepareIsolatedStorageTest<{ - items: LiveList; - }>( + await prepareIsolatedStorageTest<{ items: LiveList }>( [ createSerializedRoot(), createSerializedList("0:1", "root", "items"), diff --git a/packages/liveblocks-core/src/crdts/__tests__/LiveObject.devserver.test.ts b/packages/liveblocks-core/src/crdts/__tests__/LiveObject.devserver.test.ts index f9b424ad8e6..09a005fbcde 100644 --- a/packages/liveblocks-core/src/crdts/__tests__/LiveObject.devserver.test.ts +++ b/packages/liveblocks-core/src/crdts/__tests__/LiveObject.devserver.test.ts @@ -101,9 +101,7 @@ describe("LiveObject", () => { }); test("set with same value is a no-op", async () => { - const { root, room } = await prepareIsolatedStorageTest<{ - a: number; - }>({ + const { root, room } = await prepareIsolatedStorageTest<{ a: number }>({ liveblocksType: "LiveObject", data: { a: 1 }, }); @@ -118,9 +116,7 @@ describe("LiveObject", () => { }); test("set with different value creates an undo entry", async () => { - const { root, room } = await prepareIsolatedStorageTest<{ - a: number; - }>({ + const { root, room } = await prepareIsolatedStorageTest<{ a: number }>({ liveblocksType: "LiveObject", data: { a: 1 }, }); @@ -651,9 +647,7 @@ describe("LiveObject", () => { }); test("should not notify if property does not exist", async () => { - const { room, root } = await prepareIsolatedStorageTest<{ - a?: number; - }>(); + const { room, root } = await prepareIsolatedStorageTest<{ a?: number }>(); const callback = vi.fn(); room.subscribe(root, callback); @@ -664,9 +658,7 @@ describe("LiveObject", () => { }); test("should notify if property has been deleted", async () => { - const { room, root } = await prepareIsolatedStorageTest<{ - a?: number; - }>({ + const { room, root } = await prepareIsolatedStorageTest<{ a?: number }>({ liveblocksType: "LiveObject", data: { a: 1 }, }); diff --git a/packages/liveblocks-core/src/room.ts b/packages/liveblocks-core/src/room.ts index 4667949a3e8..d4e3e357c60 100644 --- a/packages/liveblocks-core/src/room.ts +++ b/packages/liveblocks-core/src/room.ts @@ -1336,8 +1336,8 @@ type RoomState< pool: ManagedPool; root: LiveObject | undefined; - readonly undoStack: Stackframe

[][]; - readonly redoStack: Stackframe

[][]; + undoStack: Stackframe

[][]; + redoStack: Stackframe

[][]; /** * When history is paused, all operations will get queued up here. When @@ -1891,7 +1891,7 @@ export function createRoom< // Populate missing top-level keys using `initialStorage` const root = context.root; - withoutHistory(() => { + disableHistory(() => { for (const key in context.initialStorage) { if (root.get(key) === undefined) { if (canWrite) { @@ -2287,7 +2287,9 @@ export function createRoom< function canUndo() { return context.undoStack.length > 0; } // prettier-ignore function canRedo() { return context.redoStack.length > 0; } // prettier-ignore + function onHistoryChange() { + if (historyDisabled > 0) return; eventHub.history.notify({ canUndo: canUndo(), canRedo: canRedo() }); } @@ -3361,14 +3363,26 @@ export function createRoom< commitPausedHistoryToUndoStack(); } - function withoutHistory(fn: () => T): T { - const undoBefore = context.undoStack.length; - const redoBefore = context.redoStack.length; + // Depth counter for nested history.disable() calls, 0 means history is not disabled + let historyDisabled = 0; + + function disableHistory(fn: () => T): T { + const origUndo = context.undoStack; + const origRedo = context.redoStack; + const tempUndo: Stackframe

[][] = []; + const tempRedo: Stackframe

[][] = []; + context.undoStack = tempUndo; + context.redoStack = tempRedo; + historyDisabled++; try { return fn(); } finally { - context.undoStack.length = undoBefore; - context.redoStack.length = redoBefore; + historyDisabled--; + if (context.undoStack !== tempUndo || context.redoStack !== tempRedo) { + throw new Error("unexpected stack swap during history.disable()"); // eslint-disable-line no-unsafe-finally + } + context.undoStack = origUndo; + context.redoStack = origRedo; } } @@ -3805,7 +3819,7 @@ export function createRoom< clear, pause: pauseHistory, resume: resumeHistory, - disable: withoutHistory, + disable: disableHistory, }, fetchYDoc, diff --git a/packages/liveblocks-core/test-d/ToJson.test-d.ts b/packages/liveblocks-core/test-d/ToJson.test-d.ts index 39d24dae104..d72a434520e 100644 --- a/packages/liveblocks-core/test-d/ToJson.test-d.ts +++ b/packages/liveblocks-core/test-d/ToJson.test-d.ts @@ -93,7 +93,6 @@ describe("ToJson", () => { readonly a: number; readonly b: string | undefined; }>(); - }); test("LiveObject with mixed fields (docstring example)", () => { From da15cb3baa2051765b1214ff53afebb73303926f Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Wed, 15 Apr 2026 12:19:24 +0200 Subject: [PATCH 2/7] Complete Vitest migration for `@liveblocks/react` and remaining workspaces (#2593) Co-authored-by: Marc Bouchenoire --- e2e/next-sandbox/jest.config.js | 11 - e2e/next-sandbox/package.json | 2 - e2e/node-sandbox/test/client.test.ts | 2 - package-lock.json | 1363 +++++++------- .../liveblocks-chat-sdk-adapter/package.json | 6 +- packages/liveblocks-client/package.json | 6 +- packages/liveblocks-core/package.json | 2 +- packages/liveblocks-core/turbo.json | 1 - packages/liveblocks-emails/package.json | 6 +- packages/liveblocks-node-lexical/package.json | 6 +- .../liveblocks-node-prosemirror/package.json | 6 +- packages/liveblocks-node/package.json | 6 +- .../liveblocks-react-blocknote/package.json | 6 +- .../src/__tests__/flow.test.tsx | 84 +- .../liveblocks-react-flow/vitest.config.ts | 1 - .../liveblocks-react-lexical/package.json | 6 +- packages/liveblocks-react-tiptap/package.json | 6 +- packages/liveblocks-react-ui/package.json | 5 +- .../src/__tests__/_utils.tsx | 41 +- .../src/__tests__/index.test.tsx | 2 + .../primitives/__tests__/Duration.test.tsx | 14 +- .../primitives/__tests__/Timestamp.test.tsx | 14 +- packages/liveblocks-react-ui/vitest.setup.ts | 7 + packages/liveblocks-react/.eslintrc.cjs | 1 + packages/liveblocks-react/jest.config.cjs | 1 - packages/liveblocks-react/package.json | 13 +- .../src/__tests__/PaginatedResource.test.ts | 45 +- .../src/__tests__/ThreadDB.test.ts | 8 +- .../src/__tests__/_MockWebSocket.ts | 5 +- .../src/__tests__/_restMocks.ts | 177 +- .../liveblocks-react/src/__tests__/_utils.tsx | 35 +- .../src/__tests__/index.test.tsx | 38 +- .../umbrella-store/addReaction.test.ts | 14 +- ...pdates_forUserNotificationSettings.test.ts | 17 +- .../applyThreadDeltaUpdates.test.tsx | 15 +- .../compareInboxNotifications.test.ts | 13 +- .../umbrella-store/deleteComment.test.ts | 12 +- .../__tests__/umbrella-store/index.test.ts | 5 +- .../umbrella-store/upsertComment.test.ts | 18 +- .../src/__tests__/useCreateComment.test.tsx | 277 ++- .../src/__tests__/useCreateThread.test.tsx | 240 ++- .../useDeleteAllInboxNotifications.test.tsx | 206 ++- .../useDeleteInboxNotification.test.tsx | 202 +- .../src/__tests__/useDeleteThread.test.tsx | 126 +- .../src/__tests__/useEditComment.test.tsx | 175 +- .../__tests__/useEditCommentMetadata.test.tsx | 96 +- .../__tests__/useEditThreadMetadata.test.tsx | 99 +- .../src/__tests__/useGroup.test.tsx | 209 ++- .../src/__tests__/useGroupInfo.test.tsx | 100 +- .../src/__tests__/useHistoryVersions.test.tsx | 218 ++- .../useInboxNotificationThread.test.tsx | 93 +- .../__tests__/useInboxNotifications.test.tsx | 926 +++++----- .../src/__tests__/useInitial.test.tsx | 29 +- ...seMarkAllInboxNotificationsAsRead.test.tsx | 133 +- .../useMarkInboxNotificationAsRead.test.tsx | 54 +- .../__tests__/useMarkThreadAsRead.test.tsx | 87 +- .../useMarkThreadAsResolved.test.tsx | 59 +- .../useMarkThreadAsUnresolved.test.tsx | 61 +- .../__tests__/useMentionSuggestions.test.tsx | 45 +- .../useNotificationSettings.test.tsx | 483 +++-- .../src/__tests__/useRoomInfo.test.tsx | 100 +- .../useRoomSubscriptionSettings.test.tsx | 255 ++- .../__tests__/useSubscribeToThread.test.tsx | 136 +- .../__tests__/useThreadSubscription.test.tsx | 153 +- .../src/__tests__/useThreads.test.tsx | 1621 ++++++++--------- .../useUnreadInboxNotificationsCount.test.tsx | 63 +- .../useUnsubscribeFromThread.test.tsx | 121 +- .../src/__tests__/useUrlMetadata.test.tsx | 120 +- .../src/__tests__/useUser.test.tsx | 70 +- .../src/__tests__/useUserThreads.test.tsx | 460 +++-- packages/liveblocks-react/vitest.config.ts | 19 + packages/liveblocks-react/vitest.setup.ts | 8 + shared/jest-config/fetch-polyfill.js | 6 - shared/jest-config/index.js | 41 - shared/jest-config/package.json | 13 - tools/liveblocks-codemod/jest.config.js | 11 - tools/liveblocks-codemod/package.json | 8 +- .../react-comments-to-react-ui.ts.test.ts | 5 +- .../live-list-constructor/from-core.input.tsx | 0 .../from-core.output.tsx | 0 .../live-list-constructor/general.input.tsx | 0 .../live-list-constructor/general.output.tsx | 0 .../renamed-local.input.tsx | 0 .../renamed-local.output.tsx | 0 .../live-list-constructor/unrelated.input.tsx | 0 .../unrelated.output.tsx | 0 .../liveblocks-ui-config/general.input.tsx | 0 .../liveblocks-ui-config/general.output.tsx | 0 .../general.input.tsx | 0 .../general.output.tsx | 0 .../imports-suspense.input.tsx | 0 .../imports-suspense.output.tsx | 0 .../imports.input.tsx | 0 .../imports.output.tsx | 0 .../liveblocks-no-types.config.input.tsx | 0 .../liveblocks-no-types.config.output.tsx | 0 .../liveblocks.config.input.tsx | 0 .../liveblocks.config.output.tsx | 0 .../general.input.tsx | 0 .../general.output.tsx | 0 .../renamed-local.input.tsx | 0 .../renamed-local.output.tsx | 0 .../typed.input.tsx | 0 .../typed.output.tsx | 0 .../unrelated.input.tsx | 0 .../unrelated.output.tsx | 0 .../general.input.tsx | 0 .../general.output.tsx | 0 .../remove-yjs-default-export/named.input.tsx | 0 .../named.output.tsx | 0 .../unrelated.input.tsx | 0 .../unrelated.output.tsx | 0 .../client-unrelated.input.tsx | 0 .../client-unrelated.output.tsx | 0 .../client.input.tsx | 0 .../client.output.tsx | 0 .../node-unrelated.input.tsx | 0 .../node-unrelated.output.tsx | 0 .../node.input.tsx | 0 .../node.output.tsx | 0 .../react-alt-liveblocks.config.input.tsx | 0 .../react-alt-liveblocks.config.output.tsx | 0 .../react-liveblocks.config.input.tsx | 0 .../react-liveblocks.config.output.tsx | 0 .../react-unrelated.input.tsx | 0 .../react-unrelated.output.tsx | 0 .../react.input.tsx | 0 .../react.output.tsx | 0 .../room-info-to-room-data/general.input.tsx | 0 .../room-info-to-room-data/general.output.tsx | 0 .../import-type.input.tsx | 0 .../import-type.output.tsx | 0 .../room-info-to-room-data/type.input.tsx | 0 .../room-info-to-room-data/type.output.tsx | 0 .../unrelated.input.tsx | 0 .../unrelated.output.tsx | 0 .../comments.input.tsx | 0 .../comments.output.tsx | 0 .../src/transforms/__tests__/_utils.ts | 65 + .../__tests__/live-list-constructor.test.ts | 19 - .../__tests__/liveblocks-ui-config.test.ts | 19 - .../react-comments-to-react-ui.test.ts | 19 - .../remove-liveblocks-config-contexts.test.ts | 27 - .../remove-unneeded-type-params.test.ts | 19 - .../remove-yjs-default-export.test.ts | 19 - .../rename-notification-settings.test.ts | 19 - .../__tests__/room-info-to-room-data.test.ts | 19 - ...lify-client-side-suspense-children.test.ts | 27 - .../transforms/__tests__/transforms.test.ts | 19 + tools/liveblocks-codemod/tsconfig.json | 3 +- tools/liveblocks-codemod/vitest.config.ts | 14 + 151 files changed, 4561 insertions(+), 4875 deletions(-) delete mode 100644 e2e/next-sandbox/jest.config.js delete mode 100644 packages/liveblocks-react/jest.config.cjs create mode 100644 packages/liveblocks-react/vitest.config.ts create mode 100644 packages/liveblocks-react/vitest.setup.ts delete mode 100644 shared/jest-config/fetch-polyfill.js delete mode 100644 shared/jest-config/index.js delete mode 100644 shared/jest-config/package.json delete mode 100644 tools/liveblocks-codemod/jest.config.js rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/live-list-constructor/from-core.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/live-list-constructor/from-core.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/live-list-constructor/general.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/live-list-constructor/general.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/live-list-constructor/renamed-local.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/live-list-constructor/renamed-local.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/live-list-constructor/unrelated.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/live-list-constructor/unrelated.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/liveblocks-ui-config/general.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/liveblocks-ui-config/general.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/react-comments-to-react-ui/general.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/react-comments-to-react-ui/general.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-liveblocks-config-contexts/imports-suspense.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-liveblocks-config-contexts/imports-suspense.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-liveblocks-config-contexts/imports.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-liveblocks-config-contexts/imports.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-liveblocks-config-contexts/liveblocks-no-types.config.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-liveblocks-config-contexts/liveblocks-no-types.config.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-liveblocks-config-contexts/liveblocks.config.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-liveblocks-config-contexts/liveblocks.config.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-unneeded-type-params/general.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-unneeded-type-params/general.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-unneeded-type-params/renamed-local.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-unneeded-type-params/renamed-local.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-unneeded-type-params/typed.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-unneeded-type-params/typed.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-unneeded-type-params/unrelated.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-unneeded-type-params/unrelated.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-yjs-default-export/general.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-yjs-default-export/general.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-yjs-default-export/named.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-yjs-default-export/named.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-yjs-default-export/unrelated.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/remove-yjs-default-export/unrelated.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/client-unrelated.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/client-unrelated.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/client.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/client.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/node-unrelated.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/node-unrelated.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/node.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/node.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/react-alt-liveblocks.config.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/react-alt-liveblocks.config.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/react-liveblocks.config.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/react-liveblocks.config.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/react-unrelated.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/react-unrelated.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/react.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/rename-notification-settings/react.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/room-info-to-room-data/general.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/room-info-to-room-data/general.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/room-info-to-room-data/import-type.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/room-info-to-room-data/import-type.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/room-info-to-room-data/type.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/room-info-to-room-data/type.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/room-info-to-room-data/unrelated.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/room-info-to-room-data/unrelated.output.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/simplify-client-side-suspense-children/comments.input.tsx (100%) rename tools/liveblocks-codemod/src/transforms/{__testfixtures__ => __tests__/__fixtures__}/simplify-client-side-suspense-children/comments.output.tsx (100%) create mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/_utils.ts delete mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/live-list-constructor.test.ts delete mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/liveblocks-ui-config.test.ts delete mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/react-comments-to-react-ui.test.ts delete mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/remove-liveblocks-config-contexts.test.ts delete mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/remove-unneeded-type-params.test.ts delete mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/remove-yjs-default-export.test.ts delete mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/rename-notification-settings.test.ts delete mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/room-info-to-room-data.test.ts delete mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/simplify-client-side-suspense-children.test.ts create mode 100644 tools/liveblocks-codemod/src/transforms/__tests__/transforms.test.ts create mode 100644 tools/liveblocks-codemod/vitest.config.ts diff --git a/e2e/next-sandbox/jest.config.js b/e2e/next-sandbox/jest.config.js deleted file mode 100644 index e24f1575ed8..00000000000 --- a/e2e/next-sandbox/jest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -/** @type {import('jest').Config} */ - -const commonJestConfig = require("@liveblocks/jest-config"); - -module.exports = { - // Our standard Jest configuration, used by all projects in this monorepo - ...commonJestConfig, - - testTimeout: 6000000, - verbose: true, -}; diff --git a/e2e/next-sandbox/package.json b/e2e/next-sandbox/package.json index f5b13e58f40..de2e2f2b1e3 100644 --- a/e2e/next-sandbox/package.json +++ b/e2e/next-sandbox/package.json @@ -35,9 +35,7 @@ "@eslint/compat": "^2.0.1", "@eslint/eslintrc": "^3.3.3", "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", "@playwright/test": "^1.55.0", - "@types/jest": "^29.5.14", "@types/lodash": "^4.17.13", "eslint-config-next": "16.1.4", "lodash": "^4.17.21", diff --git a/e2e/node-sandbox/test/client.test.ts b/e2e/node-sandbox/test/client.test.ts index 1cd72e2b99f..c0c634ab10b 100644 --- a/e2e/node-sandbox/test/client.test.ts +++ b/e2e/node-sandbox/test/client.test.ts @@ -76,7 +76,6 @@ describe("@liveblocks/client package e2e", () => { process.env.PUBLIC_LIVEBLOCKS_PUBLIC_KEY ?? process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!, polyfills: { WebSocket }, - // @ts-expect-error hidden config baseUrl: process.env.NEXT_PUBLIC_LIVEBLOCKS_BASE_URL!, }); @@ -85,7 +84,6 @@ describe("@liveblocks/client package e2e", () => { process.env.PUBLIC_LIVEBLOCKS_PUBLIC_KEY ?? process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!, polyfills: { WebSocket }, - // @ts-expect-error hidden config baseUrl: process.env.NEXT_PUBLIC_LIVEBLOCKS_BASE_URL, }); diff --git a/package-lock.json b/package-lock.json index e22c7f70354..4ab8fe5c8c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -829,9 +829,7 @@ "@eslint/compat": "^2.0.1", "@eslint/eslintrc": "^3.3.3", "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", "@playwright/test": "^1.55.0", - "@types/jest": "^29.5.14", "@types/lodash": "^4.17.13", "eslint-config-next": "16.1.4", "lodash": "^4.17.21", @@ -1805,7 +1803,10 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1815,7 +1816,10 @@ }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1825,7 +1829,10 @@ }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -1848,7 +1855,10 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1858,7 +1868,10 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1881,7 +1894,10 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1901,7 +1917,10 @@ }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1911,7 +1930,10 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1921,7 +1943,10 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1941,7 +1966,10 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -2153,7 +2181,10 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@blocknote/core": { "version": "0.47.0", @@ -4184,7 +4215,10 @@ }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", + "dev": true, "license": "ISC", + "optional": true, + "peer": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -4198,14 +4232,20 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { "version": "1.0.10", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -4216,7 +4256,10 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { "version": "3.14.1", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -4227,7 +4270,10 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -4237,7 +4283,10 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { "version": "2.3.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -4250,7 +4299,10 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { "version": "4.1.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -4260,13 +4312,17 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8" } }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4274,7 +4330,10 @@ }, "node_modules/@jest/console": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -4289,7 +4348,10 @@ }, "node_modules/@jest/console/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4303,7 +4365,10 @@ }, "node_modules/@jest/core": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -4348,7 +4413,10 @@ }, "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4362,7 +4430,10 @@ }, "node_modules/@jest/core/node_modules/jest-config": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -4405,7 +4476,10 @@ }, "node_modules/@jest/environment": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -4418,7 +4492,10 @@ }, "node_modules/@jest/expect": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -4429,6 +4506,7 @@ }, "node_modules/@jest/expect-utils": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3" @@ -4439,7 +4517,10 @@ }, "node_modules/@jest/fake-timers": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -4454,7 +4535,10 @@ }, "node_modules/@jest/globals": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -4467,7 +4551,10 @@ }, "node_modules/@jest/reporters": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -4508,7 +4595,10 @@ }, "node_modules/@jest/reporters/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4522,7 +4612,10 @@ }, "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { "version": "6.0.1", + "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -4536,6 +4629,7 @@ }, "node_modules/@jest/schemas": { "version": "29.6.3", + "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -4546,7 +4640,10 @@ }, "node_modules/@jest/source-map": { "version": "29.6.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -4558,7 +4655,10 @@ }, "node_modules/@jest/test-result": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -4571,7 +4671,10 @@ }, "node_modules/@jest/test-sequencer": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -4584,7 +4687,10 @@ }, "node_modules/@jest/transform": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -4608,7 +4714,10 @@ }, "node_modules/@jest/transform/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4622,6 +4731,7 @@ }, "node_modules/@jest/types": { "version": "29.6.3", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -4637,6 +4747,7 @@ }, "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5044,10 +5155,6 @@ "resolved": "shared/eslint-config", "link": true }, - "node_modules/@liveblocks/jest-config": { - "resolved": "shared/jest-config", - "link": true - }, "node_modules/@liveblocks/next-ai-kitchen-sink": { "resolved": "e2e/next-ai-kitchen-sink", "link": true @@ -5176,20 +5283,6 @@ "darwin" ] }, - "node_modules/@mswjs/cookies": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.2.tgz", - "integrity": "sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/set-cookie-parser": "^2.4.0", - "set-cookie-parser": "^2.4.6" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@mswjs/interceptors": { "version": "0.39.5", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.5.tgz", @@ -15733,6 +15826,7 @@ }, "node_modules/@sinclair/typebox": { "version": "0.27.8", + "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { @@ -15747,14 +15841,20 @@ }, "node_modules/@sinonjs/commons": { "version": "3.0.0", + "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { "version": "10.3.0", + "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -17263,7 +17363,10 @@ }, "node_modules/@tootallnate/once": { "version": "2.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 10" } @@ -17329,7 +17432,10 @@ }, "node_modules/@types/babel__core": { "version": "7.20.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -17340,14 +17446,20 @@ }, "node_modules/@types/babel__generator": { "version": "7.6.6", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { "version": "7.4.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -17355,7 +17467,10 @@ }, "node_modules/@types/babel__traverse": { "version": "7.20.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -17612,7 +17727,10 @@ }, "node_modules/@types/graceful-fs": { "version": "4.1.8", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/node": "*" } @@ -17638,10 +17756,12 @@ }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", + "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.0", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -17649,6 +17769,7 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.1", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -17656,19 +17777,13 @@ }, "node_modules/@types/jest": { "version": "29.5.14", + "dev": true, "license": "MIT", "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, - "node_modules/@types/js-levenshtein": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.3.tgz", - "integrity": "sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/jscodeshift": { "version": "0.11.11", "dev": true, @@ -17678,15 +17793,6 @@ "recast": "^0.20.3" } }, - "node_modules/@types/jsdom": { - "version": "20.0.0", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -17746,6 +17852,7 @@ }, "node_modules/@types/node": { "version": "18.19.70", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -17800,18 +17907,9 @@ "@types/react": "^18.0.0" } }, - "node_modules/@types/set-cookie-parser": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz", - "integrity": "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/stack-utils": { "version": "2.0.1", + "dev": true, "license": "MIT" }, "node_modules/@types/statuses": { @@ -17829,6 +17927,7 @@ }, "node_modules/@types/tough-cookie": { "version": "4.0.5", + "dev": true, "license": "MIT" }, "node_modules/@types/trusted-types": { @@ -17863,6 +17962,7 @@ }, "node_modules/@types/yargs": { "version": "17.0.13", + "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -17870,6 +17970,7 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.0", + "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -18459,16 +18560,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@xyflow/react": { "version": "12.10.1", "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.1.tgz", @@ -18501,17 +18592,12 @@ "d3-zoom": "^3.0.0" } }, - "node_modules/@zxing/text-encoding": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", - "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", - "dev": true, - "license": "(Unlicense OR Apache-2.0)", - "optional": true - }, "node_modules/abab": { "version": "2.0.6", - "license": "BSD-3-Clause" + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/abortcontroller-polyfill": { "version": "1.7.5", @@ -18568,7 +18654,10 @@ }, "node_modules/acorn-globals": { "version": "7.0.1", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" @@ -18599,6 +18688,7 @@ }, "node_modules/acorn-walk": { "version": "8.3.4", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -18609,7 +18699,10 @@ }, "node_modules/agent-base": { "version": "6.0.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "debug": "4" }, @@ -18982,7 +19075,9 @@ }, "node_modules/async": { "version": "3.2.6", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -19095,7 +19190,10 @@ }, "node_modules/babel-jest": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -19114,7 +19212,10 @@ }, "node_modules/babel-jest/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -19128,7 +19229,10 @@ }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", + "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -19142,7 +19246,10 @@ }, "node_modules/babel-plugin-jest-hoist": { "version": "29.6.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -19155,7 +19262,10 @@ }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -19176,7 +19286,10 @@ }, "node_modules/babel-preset-jest": { "version": "29.6.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -19269,16 +19382,6 @@ "node": ">=8" } }, - "node_modules/bl": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/bluebird": { "version": "3.7.2", "license": "MIT" @@ -19385,46 +19488,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bser": { "version": "2.1.1", + "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "license": "MIT" @@ -19628,6 +19701,7 @@ }, "node_modules/char-regex": { "version": "1.0.2", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -19736,10 +19810,12 @@ }, "node_modules/ci-info": { "version": "3.5.0", + "dev": true, "license": "MIT" }, "node_modules/cjs-module-lexer": { "version": "1.2.3", + "dev": true, "license": "MIT" }, "node_modules/class-utils": { @@ -19908,6 +19984,7 @@ }, "node_modules/cliui": { "version": "8.0.1", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -20056,7 +20133,10 @@ }, "node_modules/co": { "version": "4.6.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -20087,7 +20167,10 @@ }, "node_modules/collect-v8-coverage": { "version": "1.0.2", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/collection-visit": { "version": "1.0.0", @@ -20273,16 +20356,6 @@ "version": "2.0.0", "license": "MIT" }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", @@ -20340,7 +20413,10 @@ }, "node_modules/create-jest": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -20359,7 +20435,10 @@ }, "node_modules/create-jest/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20373,7 +20452,10 @@ }, "node_modules/create-jest/node_modules/jest-config": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -20553,11 +20635,17 @@ }, "node_modules/cssom": { "version": "0.5.0", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/cssstyle": { "version": "2.3.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "cssom": "~0.3.6" }, @@ -20567,7 +20655,10 @@ }, "node_modules/cssstyle/node_modules/cssom": { "version": "0.3.8", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/csstype": { "version": "3.2.3", @@ -20669,7 +20760,10 @@ }, "node_modules/data-urls": { "version": "3.0.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", @@ -20781,7 +20875,10 @@ }, "node_modules/decimal.js": { "version": "10.4.1", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/decode-named-character-reference": { "version": "1.3.0", @@ -20828,7 +20925,10 @@ }, "node_modules/dedent": { "version": "1.5.1", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -21113,7 +21213,10 @@ }, "node_modules/detect-newline": { "version": "3.1.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -21142,6 +21245,7 @@ }, "node_modules/diff-sequences": { "version": "29.6.3", + "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -21228,7 +21332,10 @@ }, "node_modules/domexception": { "version": "4.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "webidl-conversions": "^7.0.0" }, @@ -21297,6 +21404,8 @@ "node_modules/ejs": { "version": "3.1.10", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "jake": "^10.8.5" }, @@ -21313,7 +21422,10 @@ }, "node_modules/emittery": { "version": "0.13.1", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=12" }, @@ -22036,7 +22148,10 @@ }, "node_modules/escodegen": { "version": "2.0.0", + "dev": true, "license": "BSD-2-Clause", + "optional": true, + "peer": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -22056,7 +22171,10 @@ }, "node_modules/escodegen/node_modules/levn": { "version": "0.3.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -22067,7 +22185,10 @@ }, "node_modules/escodegen/node_modules/optionator": { "version": "0.8.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -22082,13 +22203,19 @@ }, "node_modules/escodegen/node_modules/prelude-ls": { "version": "1.1.2", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/escodegen/node_modules/type-check": { "version": "0.3.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "prelude-ls": "~1.1.2" }, @@ -22626,6 +22753,9 @@ }, "node_modules/exit": { "version": "0.1.2", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -22697,6 +22827,7 @@ }, "node_modules/expect": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "@jest/expect-utils": "^29.7.0", @@ -23019,7 +23150,10 @@ }, "node_modules/fb-watchman": { "version": "2.0.2", + "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "bser": "2.1.1" } @@ -23061,6 +23195,8 @@ "node_modules/filelist": { "version": "1.0.4", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "minimatch": "^5.0.1" } @@ -23068,6 +23204,8 @@ "node_modules/filelist/node_modules/brace-expansion": { "version": "2.0.1", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -23075,6 +23213,8 @@ "node_modules/filelist/node_modules/minimatch": { "version": "5.1.6", "license": "ISC", + "optional": true, + "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -23431,6 +23571,7 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -23469,7 +23610,10 @@ }, "node_modules/get-package-type": { "version": "0.1.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8.0.0" } @@ -24201,7 +24345,10 @@ }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "whatwg-encoding": "^2.0.0" }, @@ -24211,6 +24358,7 @@ }, "node_modules/html-escaper": { "version": "2.0.2", + "dev": true, "license": "MIT" }, "node_modules/html-tags": { @@ -24365,7 +24513,10 @@ }, "node_modules/http-proxy-agent": { "version": "5.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@tootallnate/once": "2", "agent-base": "6", @@ -24398,7 +24549,10 @@ }, "node_modules/https-proxy-agent": { "version": "5.0.1", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -24500,7 +24654,10 @@ }, "node_modules/import-local": { "version": "3.1.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -24549,65 +24706,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "license": "MIT", @@ -24887,7 +24985,10 @@ }, "node_modules/is-generator-fn": { "version": "2.1.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -24996,14 +25097,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-json": { "version": "2.0.1", "license": "ISC" @@ -25191,17 +25284,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakmap": { "version": "2.0.2", "license": "MIT", @@ -25297,6 +25379,7 @@ }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=8" @@ -25304,7 +25387,10 @@ }, "node_modules/istanbul-lib-instrument": { "version": "5.2.1", + "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -25318,13 +25404,17 @@ }, "node_modules/istanbul-lib-instrument/node_modules/semver": { "version": "6.3.1", + "dev": true, "license": "ISC", + "optional": true, + "peer": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/istanbul-lib-report": { "version": "3.0.1", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", @@ -25337,7 +25427,10 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", + "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -25349,6 +25442,7 @@ }, "node_modules/istanbul-reports": { "version": "3.1.7", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", @@ -25397,6 +25491,8 @@ "node_modules/jake": { "version": "10.9.2", "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -25413,6 +25509,8 @@ "node_modules/jake/node_modules/chalk": { "version": "4.1.2", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -25426,7 +25524,10 @@ }, "node_modules/jest": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -25450,7 +25551,10 @@ }, "node_modules/jest-changed-files": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -25462,7 +25566,10 @@ }, "node_modules/jest-circus": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -25491,7 +25598,10 @@ }, "node_modules/jest-circus/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -25505,7 +25615,10 @@ }, "node_modules/jest-cli": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -25536,7 +25649,10 @@ }, "node_modules/jest-cli/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -25550,7 +25666,10 @@ }, "node_modules/jest-cli/node_modules/jest-config": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -25593,6 +25712,7 @@ }, "node_modules/jest-diff": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -25606,6 +25726,7 @@ }, "node_modules/jest-diff/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -25620,7 +25741,10 @@ }, "node_modules/jest-docblock": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -25630,7 +25754,10 @@ }, "node_modules/jest-each": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -25644,7 +25771,10 @@ }, "node_modules/jest-each/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -25656,34 +25786,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-environment-jsdom": { - "version": "29.7.0", - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0", - "jsdom": "^20.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, "node_modules/jest-environment-node": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -25698,6 +25806,7 @@ }, "node_modules/jest-get-type": { "version": "29.6.3", + "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -25705,7 +25814,10 @@ }, "node_modules/jest-haste-map": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -25728,7 +25840,10 @@ }, "node_modules/jest-leak-detector": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -25739,6 +25854,7 @@ }, "node_modules/jest-matcher-utils": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -25752,6 +25868,7 @@ }, "node_modules/jest-matcher-utils/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -25766,6 +25883,7 @@ }, "node_modules/jest-message-util": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", @@ -25784,6 +25902,7 @@ }, "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -25798,7 +25917,10 @@ }, "node_modules/jest-mock": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -25810,7 +25932,10 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" }, @@ -25825,14 +25950,20 @@ }, "node_modules/jest-regex-util": { "version": "29.6.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -25850,7 +25981,10 @@ }, "node_modules/jest-resolve-dependencies": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -25861,7 +25995,10 @@ }, "node_modules/jest-resolve/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -25875,7 +26012,10 @@ }, "node_modules/jest-runner": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -25905,7 +26045,10 @@ }, "node_modules/jest-runner/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -25919,7 +26062,10 @@ }, "node_modules/jest-runtime": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -25950,7 +26096,10 @@ }, "node_modules/jest-runtime/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -25964,7 +26113,10 @@ }, "node_modules/jest-snapshot": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -25993,7 +26145,10 @@ }, "node_modules/jest-snapshot/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -26007,6 +26162,7 @@ }, "node_modules/jest-util": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -26022,6 +26178,7 @@ }, "node_modules/jest-util/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -26036,7 +26193,10 @@ }, "node_modules/jest-validate": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -26051,7 +26211,10 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10" }, @@ -26061,7 +26224,10 @@ }, "node_modules/jest-validate/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -26075,7 +26241,10 @@ }, "node_modules/jest-watcher": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -26092,7 +26261,10 @@ }, "node_modules/jest-watcher/node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -26106,7 +26278,10 @@ }, "node_modules/jest-worker": { "version": "29.7.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -26119,7 +26294,10 @@ }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -26149,16 +26327,6 @@ "version": "3.7.7", "license": "BSD-3-Clause" }, - "node_modules/js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -26350,7 +26518,10 @@ }, "node_modules/jsdom": { "version": "20.0.1", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.0", @@ -26581,7 +26752,10 @@ }, "node_modules/leven": { "version": "3.1.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -26945,10 +27119,6 @@ "version": "4.3.0", "license": "MIT" }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "license": "MIT" @@ -26962,36 +27132,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -27066,6 +27206,7 @@ }, "node_modules/make-dir": { "version": "4.0.0", + "dev": true, "license": "MIT", "dependencies": { "semver": "^7.5.3" @@ -27077,13 +27218,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-error": { - "version": "1.3.6", - "license": "ISC" - }, "node_modules/makeerror": { "version": "1.0.12", + "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -28600,7 +28740,10 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/node-releases": { "version": "2.0.14", @@ -28675,7 +28818,10 @@ }, "node_modules/nwsapi": { "version": "2.2.2", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/object-assign": { "version": "4.1.1", @@ -28934,43 +29080,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/ordered-binary": { "version": "1.4.1", "license": "MIT" @@ -29096,6 +29205,7 @@ "node_modules/parse5": { "version": "7.1.1", "license": "MIT", + "peer": true, "dependencies": { "entities": "^4.4.0" }, @@ -29266,7 +29376,10 @@ }, "node_modules/pkg-dir": { "version": "4.2.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -29276,7 +29389,10 @@ }, "node_modules/pkg-dir/node_modules/find-up": { "version": "4.1.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -29287,7 +29403,10 @@ }, "node_modules/pkg-dir/node_modules/locate-path": { "version": "5.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -29297,7 +29416,10 @@ }, "node_modules/pkg-dir/node_modules/p-limit": { "version": "2.3.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -29310,7 +29432,10 @@ }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "4.1.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -29795,6 +29920,7 @@ }, "node_modules/pretty-format": { "version": "29.7.0", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -29807,6 +29933,7 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -30122,6 +30249,7 @@ }, "node_modules/psl": { "version": "1.9.0", + "dev": true, "license": "MIT" }, "node_modules/publint": { @@ -30176,6 +30304,7 @@ }, "node_modules/pure-rand": { "version": "6.1.0", + "dev": true, "funding": [ { "type": "individual", @@ -30186,10 +30315,13 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/querystringify": { "version": "2.2.0", + "dev": true, "license": "MIT" }, "node_modules/queue-microtask": { @@ -30993,6 +31125,7 @@ }, "node_modules/react-is": { "version": "18.2.0", + "dev": true, "license": "MIT" }, "node_modules/react-refresh": { @@ -31552,6 +31685,7 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -31567,6 +31701,7 @@ }, "node_modules/requires-port": { "version": "1.0.0", + "dev": true, "license": "MIT" }, "node_modules/resolve": { @@ -31590,7 +31725,10 @@ }, "node_modules/resolve-cwd": { "version": "3.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "resolve-from": "^5.0.0" }, @@ -31600,7 +31738,10 @@ }, "node_modules/resolve-cwd/node_modules/resolve-from": { "version": "5.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -31626,7 +31767,10 @@ }, "node_modules/resolve.exports": { "version": "2.0.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10" } @@ -31804,14 +31948,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/sade": { "version": "1.8.1", "dev": true, @@ -32016,13 +32152,6 @@ "node": ">= 18" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "dev": true, - "license": "MIT" - }, "node_modules/set-function-length": { "version": "1.2.2", "license": "MIT", @@ -32548,7 +32677,10 @@ }, "node_modules/source-map-support": { "version": "0.5.13", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -32605,7 +32737,10 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", - "license": "BSD-3-Clause" + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/stable": { "version": "0.1.8", @@ -32618,6 +32753,7 @@ }, "node_modules/stack-utils": { "version": "2.0.5", + "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -32628,6 +32764,7 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -32776,7 +32913,10 @@ }, "node_modules/string-length": { "version": "4.0.2", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -32950,7 +33090,10 @@ }, "node_modules/strip-bom": { "version": "4.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -33750,7 +33893,10 @@ }, "node_modules/test-exclude": { "version": "6.0.0", + "dev": true, "license": "ISC", + "optional": true, + "peer": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -33919,7 +34065,10 @@ }, "node_modules/tmpl": { "version": "1.0.5", - "license": "BSD-3-Clause" + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/to-object-path": { "version": "0.3.0", @@ -33983,6 +34132,7 @@ }, "node_modules/tough-cookie": { "version": "4.1.4", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.33", @@ -33996,7 +34146,10 @@ }, "node_modules/tr46": { "version": "3.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "punycode": "^2.1.1" }, @@ -34064,62 +34217,6 @@ "version": "0.1.13", "license": "Apache-2.0" }, - "node_modules/ts-jest": { - "version": "29.2.5", - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.6.3", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.6.3", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ts-morph": { "version": "22.0.0", "dev": true, @@ -35241,7 +35338,10 @@ }, "node_modules/type-detect": { "version": "4.0.8", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -35753,6 +35853,7 @@ }, "node_modules/undici-types": { "version": "5.26.5", + "devOptional": true, "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { @@ -35913,6 +36014,7 @@ }, "node_modules/universalify": { "version": "0.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">= 4.0.0" @@ -36016,6 +36118,7 @@ }, "node_modules/url-parse": { "version": "1.5.10", + "dev": true, "license": "MIT", "dependencies": { "querystringify": "^2.1.1", @@ -36100,20 +36203,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "license": "MIT" @@ -36137,7 +36226,10 @@ }, "node_modules/v8-to-istanbul": { "version": "9.1.3", + "dev": true, "license": "ISC", + "optional": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -36864,7 +36956,10 @@ }, "node_modules/w3c-xmlserializer": { "version": "3.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "xml-name-validator": "^4.0.0" }, @@ -36874,7 +36969,10 @@ }, "node_modules/walker": { "version": "1.0.8", + "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "makeerror": "1.0.12" } @@ -36901,19 +36999,6 @@ "version": "1.2.2", "license": "MIT" }, - "node_modules/web-encoding": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", - "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "util": "^0.12.3" - }, - "optionalDependencies": { - "@zxing/text-encoding": "0.9.0" - } - }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", @@ -36939,7 +37024,10 @@ }, "node_modules/whatwg-encoding": { "version": "2.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "iconv-lite": "0.6.3" }, @@ -36949,7 +37037,10 @@ }, "node_modules/whatwg-encoding/node_modules/iconv-lite": { "version": "0.6.3", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -36963,14 +37054,20 @@ }, "node_modules/whatwg-mimetype": { "version": "3.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=12" } }, "node_modules/whatwg-url": { "version": "11.0.0", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" @@ -37092,13 +37189,17 @@ }, "node_modules/word-wrap": { "version": "1.2.5", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/wrap-ansi": { "version": "7.0.0", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -37134,7 +37235,10 @@ }, "node_modules/write-file-atomic": { "version": "4.0.2", + "dev": true, "license": "ISC", + "optional": true, + "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -37164,7 +37268,10 @@ }, "node_modules/xml-name-validator": { "version": "4.0.0", + "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "engines": { "node": ">=12" } @@ -37242,6 +37349,7 @@ }, "node_modules/y18n": { "version": "5.0.8", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -37260,6 +37368,7 @@ }, "node_modules/yargs": { "version": "17.7.2", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -37276,6 +37385,7 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -37519,15 +37629,14 @@ }, "devDependencies": { "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", "@liveblocks/query-parser": "^0.1.1", "@liveblocks/vitest-config": "*", - "@testing-library/jest-dom": "6.4.6", - "@testing-library/react": "14.1.2", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^16.3.0", "date-fns": "^3.6.0", "eslint-plugin-react-hooks": "^4.6.2", "itertools": "^2.3.2", - "msw": "^1.3.5", + "msw": "^2.10.4", "react-error-boundary": "^4.0.13" }, "peerDependencies": { @@ -40978,10 +41087,11 @@ "@liveblocks/eslint-config": "*", "@liveblocks/rollup-config": "*", "@liveblocks/vitest-config": "*", - "@testing-library/jest-dom": "^6.4.6", - "@testing-library/react": "^13.1.1", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^16.3.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", + "msw": "^2.10.4", "stylelint": "^15.10.2", "stylelint-config-standard": "^34.0.0", "stylelint-order": "^6.0.3", @@ -41032,10 +41142,39 @@ "version": "0.2.2", "license": "MIT" }, + "packages/liveblocks-react-ui/node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "packages/liveblocks-react-ui/node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "packages/liveblocks-react-ui/node_modules/@testing-library/jest-dom": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.4.tgz", - "integrity": "sha512-xDXgLjVunjHqczScfkCJ9iyjdNOVHvvCdqHSSxwM9L0l/wHkTRum67SDc020uAlCoqktJplgO2AAQeLP1wgqDQ==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, "license": "MIT", "dependencies": { @@ -41043,7 +41182,6 @@ "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", - "lodash": "^4.17.21", "picocolors": "^1.1.1", "redent": "^3.0.0" }, @@ -41053,6 +41191,58 @@ "yarn": ">=1" } }, + "packages/liveblocks-react-ui/node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/liveblocks-react-ui/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/liveblocks-react-ui/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, "packages/liveblocks-react-ui/node_modules/dom-accessibility-api": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", @@ -41079,115 +41269,61 @@ "dev": true, "license": "ISC" }, - "packages/liveblocks-react/node_modules/@mswjs/interceptors": { - "version": "0.17.10", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.17.10.tgz", - "integrity": "sha512-N8x7eSLGcmUFNWZRxT1vsHvypzIRgQYdG0rJey/rZCy6zT/30qDt8Joj7FxzGNLSwXbeZqJOMqDurp7ra4hgbw==", + "packages/liveblocks-react-ui/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@open-draft/until": "^1.0.3", - "@types/debug": "^4.1.7", - "@xmldom/xmldom": "^0.8.3", - "debug": "^4.3.3", - "headers-polyfill": "3.2.5", - "outvariant": "^1.2.1", - "strict-event-emitter": "^0.2.4", - "web-encoding": "^1.1.5" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" }, "engines": { - "node": ">=14" - } - }, - "packages/liveblocks-react/node_modules/@mswjs/interceptors/node_modules/strict-event-emitter": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz", - "integrity": "sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "events": "^3.3.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "packages/liveblocks-react/node_modules/@open-draft/until": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", - "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", - "dev": true, - "license": "MIT" - }, "packages/liveblocks-react/node_modules/@testing-library/dom": { - "version": "9.3.4", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", + "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", + "picocolors": "1.1.1", "pretty-format": "^27.0.2" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "packages/liveblocks-react/node_modules/@testing-library/jest-dom": { - "version": "6.4.6", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, "license": "MIT", "dependencies": { "@adobe/css-tools": "^4.4.0", - "@babel/runtime": "^7.9.2", "aria-query": "^5.0.0", - "chalk": "^3.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", - "lodash": "^4.17.21", + "picocolors": "^1.1.1", "redent": "^3.0.0" }, "engines": { "node": ">=14", "npm": ">=6", "yarn": ">=1" - }, - "peerDependencies": { - "@jest/globals": ">= 28", - "@types/bun": "latest", - "@types/jest": ">= 28", - "jest": ">= 28", - "vitest": ">= 0.32" - }, - "peerDependenciesMeta": { - "@jest/globals": { - "optional": true - }, - "@types/bun": { - "optional": true - }, - "@types/jest": { - "optional": true - }, - "jest": { - "optional": true - }, - "vitest": { - "optional": true - } - } - }, - "packages/liveblocks-react/node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" } }, "packages/liveblocks-react/node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { @@ -41196,145 +41332,41 @@ "license": "MIT" }, "packages/liveblocks-react/node_modules/@testing-library/react": { - "version": "14.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^9.0.0", - "@types/react-dom": "^18.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "packages/liveblocks-react/node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true, - "license": "MIT" - }, - "packages/liveblocks-react/node_modules/chalk": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "packages/liveblocks-react/node_modules/headers-polyfill": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.2.5.tgz", - "integrity": "sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==", + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", "dev": true, - "license": "MIT" - }, - "packages/liveblocks-react/node_modules/msw": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/msw/-/msw-1.3.5.tgz", - "integrity": "sha512-nG3fpmBXxFbKSIdk6miPuL3KjU6WMxgoW4tG1YgnP1M+TRG3Qn7b7R0euKAHq4vpwARHb18ZyfZljSxsTnMX2w==", - "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "@mswjs/cookies": "^0.2.2", - "@mswjs/interceptors": "^0.17.10", - "@open-draft/until": "^1.0.3", - "@types/cookie": "^0.4.1", - "@types/js-levenshtein": "^1.1.1", - "chalk": "^4.1.1", - "chokidar": "^3.4.2", - "cookie": "^0.4.2", - "graphql": "^16.8.1", - "headers-polyfill": "3.2.5", - "inquirer": "^8.2.0", - "is-node-process": "^1.2.0", - "js-levenshtein": "^1.1.6", - "node-fetch": "^2.6.7", - "outvariant": "^1.4.0", - "path-to-regexp": "^6.3.0", - "strict-event-emitter": "^0.4.3", - "type-fest": "^2.19.0", - "yargs": "^17.3.1" - }, - "bin": { - "msw": "cli/index.js" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mswjs" + "node": ">=18" }, "peerDependencies": { - "typescript": ">= 4.4.x" + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { - "typescript": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { "optional": true } } }, - "packages/liveblocks-react/node_modules/pretty-format": { - "version": "27.5.1", + "packages/liveblocks-react/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "packages/liveblocks-react/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "packages/liveblocks-react/node_modules/react-is": { - "version": "17.0.2", - "dev": true, - "license": "MIT" - }, - "packages/liveblocks-react/node_modules/strict-event-emitter": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", - "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", - "dev": true, - "license": "MIT" - }, - "packages/liveblocks-react/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dequal": "^2.0.3" } }, "packages/liveblocks-redux": { @@ -41691,78 +41723,6 @@ } } }, - "schema-lang/codemirror-language": { - "name": "@liveblocks/codemirror-language", - "version": "0.0.13-beta1", - "extraneous": true, - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/lint": "^6.0.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "@liveblocks/schema": "0.0.13-beta1" - }, - "devDependencies": { - "@lezer/generator": "^1.2.2", - "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*" - } - }, - "schema-lang/infer-schema": { - "name": "@liveblocks/infer-schema", - "version": "0.0.13-beta1", - "extraneous": true, - "dependencies": { - "@liveblocks/core": "^1.0.0", - "@liveblocks/schema": "0.0.13-beta1", - "decoders": "^2.4.0", - "pluralize": "^8.0.0" - }, - "devDependencies": { - "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", - "@types/pluralize": "^0.0.29" - } - }, - "schema-lang/liveblocks-schema": { - "name": "@liveblocks/schema", - "version": "0.0.13-beta1", - "extraneous": true, - "dependencies": { - "didyoumean": "^1.2.2" - }, - "devDependencies": { - "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", - "@types/didyoumean": "^1.2.0", - "ast-generator": "^0.4.0", - "peggy": "^2.0.1", - "pkg": "^4.4.9", - "ts-node": "^10.9.1", - "ts-pegjs": "^2.1.0", - "watch": "^1.0.2" - } - }, - "schema-lang/textmate-grammar": { - "name": "@liveblocks/textmate-grammar", - "version": "0.0.13-beta1", - "extraneous": true - }, - "schema-lang/vscode-extension": { - "name": "@liveblocks/vscode-extension", - "version": "0.0.13-beta1", - "extraneous": true, - "dependencies": { - "@liveblocks/textmate-grammar": "*" - }, - "devDependencies": { - "esbuild": "^0.17.8", - "esbuild-plugin-copy": "^2.0.2" - }, - "engines": { - "vscode": "^1.75.0" - } - }, "shared/eslint-config": { "name": "@liveblocks/eslint-config", "dependencies": { @@ -41773,17 +41733,6 @@ "eslint-plugin-simple-import-sort": "^12.1.0" } }, - "shared/jest-config": { - "name": "@liveblocks/jest-config", - "dependencies": { - "@types/jest": "^29.5.14", - "fast-check": "^4.3.0", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", - "ts-jest": "^29.2.5", - "whatwg-fetch": "^3.6.20" - } - }, "shared/rollup-config": { "name": "@liveblocks/rollup-config", "dependencies": { @@ -42273,7 +42222,7 @@ }, "devDependencies": { "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", + "@liveblocks/vitest-config": "*", "@types/is-git-clean": "1.1.2", "@types/jscodeshift": "0.11.11" } diff --git a/packages/liveblocks-chat-sdk-adapter/package.json b/packages/liveblocks-chat-sdk-adapter/package.json index 9222f3dda97..216f50b9be2 100644 --- a/packages/liveblocks-chat-sdk-adapter/package.json +++ b/packages/liveblocks-chat-sdk-adapter/package.json @@ -30,9 +30,9 @@ "format": "(eslint --fix src/ || true) && prettier --write src/", "lint": "eslint src/", "lint:package": "publint --strict && attw --pack", - "test": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" + "test": "vitest run", + "test:ci": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@liveblocks/core": "3.18.1", diff --git a/packages/liveblocks-client/package.json b/packages/liveblocks-client/package.json index cf631f835d4..7ab0c57c46b 100644 --- a/packages/liveblocks-client/package.json +++ b/packages/liveblocks-client/package.json @@ -30,10 +30,10 @@ "format": "(eslint --fix src/ || true) && prettier --write src/", "lint": "eslint src/", "lint:package": "publint --strict && attw --pack", - "test": "NODE_OPTIONS=\"--no-deprecation\" vitest run --passWithNoTests", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" vitest run --passWithNoTests", + "test": "vitest run --passWithNoTests", + "test:ci": "vitest run --passWithNoTests", "test:types": "vitest run --config ./vitest.config.typecheck.ts", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" + "test:watch": "vitest" }, "dependencies": { "@liveblocks/core": "3.18.1" diff --git a/packages/liveblocks-core/package.json b/packages/liveblocks-core/package.json index c8bcf243bab..d72c0f1a262 100644 --- a/packages/liveblocks-core/package.json +++ b/packages/liveblocks-core/package.json @@ -40,7 +40,7 @@ "test": "npx liveblocks dev -p 1154 -c 'vitest run --coverage'", "test:ci": "vitest run", "test:types": "vitest run --config ./vitest.config.typecheck.ts", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest", + "test:watch": "vitest", "test:e2e": "npx liveblocks dev -p 1154 -c 'vitest run --config=./vitest.config.e2e.ts'", "test:deps": "depcruise src --exclude __tests__", "showdeps": "depcruise src --include-only '^src' --exclude='__tests__' --output-type dot | dot -T svg > /tmp/dependency-graph.svg && open /tmp/dependency-graph.svg", diff --git a/packages/liveblocks-core/turbo.json b/packages/liveblocks-core/turbo.json index 76de35cae17..21dfed47a88 100644 --- a/packages/liveblocks-core/turbo.json +++ b/packages/liveblocks-core/turbo.json @@ -11,7 +11,6 @@ "cache": false, "dependsOn": ["build"], "inputs": [ - "jest.*", "e2e/**/*.tsx", "e2e/**/*.ts", "src/**/*.tsx", diff --git a/packages/liveblocks-emails/package.json b/packages/liveblocks-emails/package.json index 2f8c53bca97..43cffecaa1a 100644 --- a/packages/liveblocks-emails/package.json +++ b/packages/liveblocks-emails/package.json @@ -31,9 +31,9 @@ "format": "(eslint --fix src/ || true) && prettier --write src/", "lint": "eslint src/", "lint:package": "publint --strict && attw --pack", - "test": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" + "test": "vitest run", + "test:ci": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@liveblocks/core": "3.18.1", diff --git a/packages/liveblocks-node-lexical/package.json b/packages/liveblocks-node-lexical/package.json index 15412f60311..8665b1e04a4 100644 --- a/packages/liveblocks-node-lexical/package.json +++ b/packages/liveblocks-node-lexical/package.json @@ -30,9 +30,9 @@ "format": "(eslint --fix src/ || true) && prettier --write src/", "lint": "eslint src/", "lint:package": "publint --strict && attw --pack", - "test": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" + "test": "vitest run", + "test:ci": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@liveblocks/core": "3.18.1", diff --git a/packages/liveblocks-node-prosemirror/package.json b/packages/liveblocks-node-prosemirror/package.json index e22d3739adb..8ab909bb9b8 100644 --- a/packages/liveblocks-node-prosemirror/package.json +++ b/packages/liveblocks-node-prosemirror/package.json @@ -30,9 +30,9 @@ "format": "(eslint --fix src/ || true) && prettier --write src/", "lint": "eslint src/", "lint:package": "publint --strict && attw --pack", - "test": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" + "test": "vitest run", + "test:ci": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@liveblocks/core": "3.18.1", diff --git a/packages/liveblocks-node/package.json b/packages/liveblocks-node/package.json index 2204669749b..1d568ace6be 100644 --- a/packages/liveblocks-node/package.json +++ b/packages/liveblocks-node/package.json @@ -30,10 +30,10 @@ "format": "(eslint --fix src/ || true) && prettier --write src/", "lint": "eslint src/", "lint:package": "publint --strict && attw --pack", - "test": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" vitest run", + "test": "vitest run", + "test:ci": "vitest run", "test:types": "vitest run --config ./vitest.config.typecheck.ts", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" + "test:watch": "vitest" }, "dependencies": { "@liveblocks/core": "3.18.1", diff --git a/packages/liveblocks-react-blocknote/package.json b/packages/liveblocks-react-blocknote/package.json index 4a21b27f867..b974df59024 100644 --- a/packages/liveblocks-react-blocknote/package.json +++ b/packages/liveblocks-react-blocknote/package.json @@ -39,9 +39,9 @@ "lint": "eslint src/; stylelint src/styles/", "lint:package": "publint --strict && attw --pack", "start": "npm run dev", - "test": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" + "test": "vitest run", + "test:ci": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@liveblocks/client": "3.18.1", diff --git a/packages/liveblocks-react-flow/src/__tests__/flow.test.tsx b/packages/liveblocks-react-flow/src/__tests__/flow.test.tsx index c87d175695a..217901fd438 100644 --- a/packages/liveblocks-react-flow/src/__tests__/flow.test.tsx +++ b/packages/liveblocks-react-flow/src/__tests__/flow.test.tsx @@ -1,9 +1,9 @@ import type { PlainLsonObject } from "@liveblocks/core"; import { useMutation } from "@liveblocks/react"; -import { act, screen, waitFor } from "@testing-library/react"; +import { act, screen } from "@testing-library/react"; import type { BuiltInEdge, BuiltInNode } from "@xyflow/react"; import { Suspense } from "react"; -import { describe, expect, test } from "vitest"; +import { describe, expect, test, vi } from "vitest"; import { useLiveblocksFlow } from "../index"; import type { LiveblocksFlow } from "../lib/types"; @@ -47,7 +47,7 @@ describe("useLiveblocksFlow", () => { }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.nodes).toHaveLength(2); expect(result.current.edges).toHaveLength(1); @@ -65,7 +65,7 @@ describe("useLiveblocksFlow", () => { test("should return empty arrays when storage is empty and no initial provided", async () => { const { result } = await renderHook(() => useLiveblocksFlow()); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.nodes).toEqual([]); expect(result.current.edges).toEqual([]); @@ -109,7 +109,7 @@ describe("useLiveblocksFlow", () => { { initialStorage: serverStorage } ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.nodes).toHaveLength(1); expect(result.current.nodes?.[0]).toMatchObject({ @@ -122,7 +122,7 @@ describe("useLiveblocksFlow", () => { test("should add node to flow when onNodesChange add is called", async () => { const { result } = await renderHook(() => useLiveblocksFlow()); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); const newNode = { id: "n1", @@ -137,7 +137,7 @@ describe("useLiveblocksFlow", () => { result.current.onNodesChange([{ type: "add", item: newNode }]); }); - await waitFor(() => expect(result.current.nodes).toHaveLength(1)); + await vi.waitFor(() => expect(result.current.nodes).toHaveLength(1)); expect(result.current.nodes?.[0]).toMatchObject({ id: "n1", position: { x: 10, y: 20 }, @@ -151,7 +151,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); const newEdge = { id: "e1-2", @@ -164,7 +164,7 @@ describe("useLiveblocksFlow", () => { result.current.onEdgesChange([{ type: "add", item: newEdge }]); }); - await waitFor(() => expect(result.current.edges).toHaveLength(1)); + await vi.waitFor(() => expect(result.current.edges).toHaveLength(1)); expect(result.current.edges?.[0]).toMatchObject({ source: "1", target: "2", @@ -180,7 +180,7 @@ describe("useLiveblocksFlow", () => { }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.edges).toHaveLength(1); @@ -191,7 +191,7 @@ describe("useLiveblocksFlow", () => { }); }); - await waitFor(() => expect(result.current.edges).toHaveLength(0)); + await vi.waitFor(() => expect(result.current.edges).toHaveLength(0)); }); test("should remove node from flow when onDelete is called", async () => { @@ -202,7 +202,7 @@ describe("useLiveblocksFlow", () => { }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.nodes).toHaveLength(2); @@ -213,7 +213,7 @@ describe("useLiveblocksFlow", () => { }); }); - await waitFor(() => expect(result.current.nodes).toHaveLength(1)); + await vi.waitFor(() => expect(result.current.nodes).toHaveLength(1)); expect(result.current.nodes?.[0]).toMatchObject({ id: "2" }); }); @@ -236,7 +236,7 @@ describe("useLiveblocksFlow", () => { const { result } = await renderHook(() => useFlowWithDelete()); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onNodesChange([ @@ -248,7 +248,7 @@ describe("useLiveblocksFlow", () => { result.current.deleteNodeFromStorage(); }); - await waitFor(() => expect(result.current.nodes).toHaveLength(1)); + await vi.waitFor(() => expect(result.current.nodes).toHaveLength(1)); expect(result.current.nodes?.[0]).toMatchObject({ id: "2" }); }); @@ -257,7 +257,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onNodesChange([ @@ -265,7 +265,7 @@ describe("useLiveblocksFlow", () => { ]); }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.nodes?.[0]?.position).toEqual({ x: 50, y: 75 }) ); }); @@ -275,7 +275,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onNodesChange([ @@ -288,7 +288,7 @@ describe("useLiveblocksFlow", () => { ]); }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.nodes?.[0]).toMatchObject({ dragging: true }) ); }); @@ -298,7 +298,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onNodesChange([ @@ -318,7 +318,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onNodesChange([ @@ -332,7 +332,7 @@ describe("useLiveblocksFlow", () => { ]); }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.nodes?.[0]).toMatchObject({ width: 200, height: 100, @@ -346,7 +346,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onNodesChange([ @@ -354,7 +354,7 @@ describe("useLiveblocksFlow", () => { ]); }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.nodes?.[0]).toMatchObject({ selected: true }) ); }); @@ -364,7 +364,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onNodesChange([ @@ -378,7 +378,7 @@ describe("useLiveblocksFlow", () => { ]); }); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.nodes?.[0]?.selected).toBeFalsy(); }); }); @@ -391,7 +391,7 @@ describe("useLiveblocksFlow", () => { }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onEdgesChange([ @@ -399,7 +399,7 @@ describe("useLiveblocksFlow", () => { ]); }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.edges?.[0]).toMatchObject({ selected: true }) ); }); @@ -409,7 +409,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onConnect({ @@ -420,7 +420,7 @@ describe("useLiveblocksFlow", () => { }); }); - await waitFor(() => expect(result.current.edges).toHaveLength(1)); + await vi.waitFor(() => expect(result.current.edges).toHaveLength(1)); act(() => { result.current.onConnect({ @@ -439,7 +439,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); act(() => { result.current.onConnect({ @@ -450,7 +450,7 @@ describe("useLiveblocksFlow", () => { }); }); - await waitFor(() => expect(result.current.edges).toHaveLength(1)); + await vi.waitFor(() => expect(result.current.edges).toHaveLength(1)); act(() => { result.current.onConnect({ @@ -461,7 +461,7 @@ describe("useLiveblocksFlow", () => { }); }); - await waitFor(() => expect(result.current.edges).toHaveLength(2)); + await vi.waitFor(() => expect(result.current.edges).toHaveLength(2)); const handles = result.current.edges?.map((e) => e.sourceHandle) ?? []; expect(handles).toContain("a"); @@ -476,7 +476,7 @@ describe("useLiveblocksFlow", () => { }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); const nodes1 = result.current.nodes; @@ -496,7 +496,7 @@ describe("useLiveblocksFlow", () => { }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); const edges1 = result.current.edges; @@ -512,7 +512,7 @@ describe("useLiveblocksFlow", () => { useLiveblocksFlow({ storageKey: "myFlow", nodes: { initial: NODES } }) ); - await waitFor(() => expect(result.current.isLoading).toBe(false)); + await vi.waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.nodes).toHaveLength(2); }); @@ -544,7 +544,9 @@ describe("useLiveblocksFlow (Suspense)", () => { expect(screen.getByTestId("fallback")).toBeInTheDocument(); - await waitFor(() => expect(screen.getByTestId("flow")).toBeInTheDocument()); + await vi.waitFor(() => + expect(screen.getByTestId("flow")).toBeInTheDocument() + ); expect(screen.getByTestId("loading").textContent).toBe("false"); expect(screen.getByTestId("node-count").textContent).toBe("2"); @@ -570,7 +572,9 @@ describe("useLiveblocksFlow (Suspense)", () => { ); - await waitFor(() => expect(screen.getByTestId("flow")).toBeInTheDocument()); + await vi.waitFor(() => + expect(screen.getByTestId("flow")).toBeInTheDocument() + ); expect(screen.getByTestId("loading").textContent).toBe("false"); expect(screen.getByTestId("node-count").textContent).toBe("0"); @@ -599,7 +603,9 @@ describe("useLiveblocksFlow (Suspense)", () => { ); - await waitFor(() => expect(screen.getByTestId("flow")).toBeInTheDocument()); + await vi.waitFor(() => + expect(screen.getByTestId("flow")).toBeInTheDocument() + ); expect(screen.getByTestId("node-label").textContent).toBe("Node 1"); expect(screen.getByTestId("edge-id").textContent).toBe("e1-2"); diff --git a/packages/liveblocks-react-flow/vitest.config.ts b/packages/liveblocks-react-flow/vitest.config.ts index b34b564def0..7dac5cb27e3 100644 --- a/packages/liveblocks-react-flow/vitest.config.ts +++ b/packages/liveblocks-react-flow/vitest.config.ts @@ -3,7 +3,6 @@ import { defaultLiveblocksVitestConfig } from "@liveblocks/vitest-config"; export default defaultLiveblocksVitestConfig({ test: { environment: "jsdom", - globals: true, setupFiles: ["vitest.setup.ts"], }, }); diff --git a/packages/liveblocks-react-lexical/package.json b/packages/liveblocks-react-lexical/package.json index 501fe0d712d..e483d5bc28d 100644 --- a/packages/liveblocks-react-lexical/package.json +++ b/packages/liveblocks-react-lexical/package.json @@ -39,9 +39,9 @@ "lint": "eslint src/; stylelint src/styles/", "lint:package": "publint --strict && attw --pack", "start": "npm run dev", - "test": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" + "test": "vitest run", + "test:ci": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@floating-ui/react-dom": "^2.1.0", diff --git a/packages/liveblocks-react-tiptap/package.json b/packages/liveblocks-react-tiptap/package.json index dc204a42d98..a50ece7e468 100644 --- a/packages/liveblocks-react-tiptap/package.json +++ b/packages/liveblocks-react-tiptap/package.json @@ -39,9 +39,9 @@ "lint": "eslint src/; stylelint src/styles/", "lint:package": "publint --strict && attw --pack", "start": "npm run dev", - "test": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" vitest run", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" + "test": "vitest run", + "test:ci": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@floating-ui/react-dom": "^2.1.0", diff --git a/packages/liveblocks-react-ui/package.json b/packages/liveblocks-react-ui/package.json index ee86159b85e..dd51d0a849e 100644 --- a/packages/liveblocks-react-ui/package.json +++ b/packages/liveblocks-react-ui/package.json @@ -107,10 +107,11 @@ "@liveblocks/eslint-config": "*", "@liveblocks/rollup-config": "*", "@liveblocks/vitest-config": "*", - "@testing-library/jest-dom": "^6.4.6", - "@testing-library/react": "^13.1.1", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^16.3.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", + "msw": "^2.10.4", "stylelint": "^15.10.2", "stylelint-config-standard": "^34.0.0", "stylelint-order": "^6.0.3", diff --git a/packages/liveblocks-react-ui/src/__tests__/_utils.tsx b/packages/liveblocks-react-ui/src/__tests__/_utils.tsx index ca2f9f4d684..5c1eeefe5ff 100644 --- a/packages/liveblocks-react-ui/src/__tests__/_utils.tsx +++ b/packages/liveblocks-react-ui/src/__tests__/_utils.tsx @@ -1,44 +1,43 @@ import type { BaseMetadata, ClientOptions, JsonObject } from "@liveblocks/core"; import { createClient } from "@liveblocks/core"; import { createLiveblocksContext, createRoomContext } from "@liveblocks/react"; -import type { RenderHookResult, RenderOptions } from "@testing-library/react"; +import type { + RenderHookOptions, + RenderHookResult, + RenderOptions, +} from "@testing-library/react"; import { render, renderHook } from "@testing-library/react"; -import type { ReactElement } from "react"; +import type { PropsWithChildren, ReactElement } from "react"; import { RoomProvider } from "./_liveblocks.config"; /** - * Testing context for all tests. Sets up a default RoomProvider to wrap all - * tests with. + * The default `RoomProvider` wrapping all tests. */ -export function AllTheProviders(props: { children: React.ReactNode }) { - return ( - ({})}> - {props.children} - - ); +export function TestingRoomProvider(props: PropsWithChildren) { + return {props.children}; } /** - * Wrapper for rendering components that are wrapped in a pre set up - * context. + * A version of `@testing-library/react`'s `renderHook` which uses + * a default `RoomProvider`. */ -function customRender(ui: ReactElement, options?: RenderOptions) { - return render(ui, { wrapper: AllTheProviders, ...options }); +function customRender(ui: ReactElement, renderOptions?: RenderOptions) { + return render(ui, { + wrapper: TestingRoomProvider, + ...renderOptions, + }); } /** - * Wrapper for rendering hooks that are wrapped in a pre set up - * context. + * A version of `@testing-library/react`'s `renderHook` which uses + * a default `RoomProvider`. */ function customRenderHook( render: (initialProps: Props) => Result, - options?: { - initialProps?: Props; - wrapper?: React.JSXElementConstructor<{ children: React.ReactElement }>; - } + options?: RenderHookOptions ): RenderHookResult { - return renderHook(render, { wrapper: AllTheProviders, ...options }); + return renderHook(render, { wrapper: TestingRoomProvider, ...options }); } export function generateFakeJwt(options: { userId: string }) { diff --git a/packages/liveblocks-react-ui/src/__tests__/index.test.tsx b/packages/liveblocks-react-ui/src/__tests__/index.test.tsx index f224aa3f8a5..f069e2e737c 100644 --- a/packages/liveblocks-react-ui/src/__tests__/index.test.tsx +++ b/packages/liveblocks-react-ui/src/__tests__/index.test.tsx @@ -15,6 +15,7 @@ const comment: CommentData = { createdAt: new Date("2023-08-14T12:41:50.243Z"), reactions: [], attachments: [], + metadata: {}, body: { version: 1, content: [ @@ -76,6 +77,7 @@ const editedComment: CommentData = { editedAt: new Date("2023-08-14T12:41:50.243Z"), reactions: [], attachments: [], + metadata: {}, body: { version: 1, content: [ diff --git a/packages/liveblocks-react-ui/src/primitives/__tests__/Duration.test.tsx b/packages/liveblocks-react-ui/src/primitives/__tests__/Duration.test.tsx index 7454fa88520..a4144147d11 100644 --- a/packages/liveblocks-react-ui/src/primitives/__tests__/Duration.test.tsx +++ b/packages/liveblocks-react-ui/src/primitives/__tests__/Duration.test.tsx @@ -1,4 +1,4 @@ -import { cleanup, render } from "@testing-library/react"; +import { act, cleanup, render } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { Duration, formatIso8601Duration } from "../Duration"; @@ -139,7 +139,9 @@ describe("Duration", () => { const time = container.querySelector("time")!; const before = time.textContent; - await vi.advanceTimersByTimeAsync(10000); + await act(async () => { + await vi.advanceTimersByTimeAsync(10000); + }); const after = time.textContent; expect(after).not.toBe(before); }); @@ -157,11 +159,15 @@ describe("Duration", () => { const time = container.querySelector("time")!; const before = time.textContent; - await vi.advanceTimersByTimeAsync(6000); + await act(async () => { + await vi.advanceTimersByTimeAsync(6000); + }); const between = time.textContent; expect(between).toBe(before); - await vi.advanceTimersByTimeAsync(6000); + await act(async () => { + await vi.advanceTimersByTimeAsync(6000); + }); const after = time.textContent; expect(after).not.toBe(before); }); diff --git a/packages/liveblocks-react-ui/src/primitives/__tests__/Timestamp.test.tsx b/packages/liveblocks-react-ui/src/primitives/__tests__/Timestamp.test.tsx index fce260a3412..1952e9705d9 100644 --- a/packages/liveblocks-react-ui/src/primitives/__tests__/Timestamp.test.tsx +++ b/packages/liveblocks-react-ui/src/primitives/__tests__/Timestamp.test.tsx @@ -1,4 +1,4 @@ -import { cleanup, render } from "@testing-library/react"; +import { act, cleanup, render } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { Timestamp } from "../Timestamp"; @@ -96,7 +96,9 @@ describe("Timestamp", () => { const time = container.querySelector("time")!; const before = time.textContent; - await vi.advanceTimersByTimeAsync(45000); + await act(async () => { + await vi.advanceTimersByTimeAsync(45000); + }); const after = time.textContent; expect(after).not.toBe(before); }); @@ -110,11 +112,15 @@ describe("Timestamp", () => { const time = container.querySelector("time")!; const before = time.textContent; - await vi.advanceTimersByTimeAsync(6000); + await act(async () => { + await vi.advanceTimersByTimeAsync(6000); + }); const between = time.textContent; expect(between).toBe(before); - await vi.advanceTimersByTimeAsync(6000); + await act(async () => { + await vi.advanceTimersByTimeAsync(6000); + }); const after = time.textContent; expect(after).not.toBe(before); }); diff --git a/packages/liveblocks-react-ui/vitest.setup.ts b/packages/liveblocks-react-ui/vitest.setup.ts index f149f27ae4b..910e4704b99 100644 --- a/packages/liveblocks-react-ui/vitest.setup.ts +++ b/packages/liveblocks-react-ui/vitest.setup.ts @@ -1 +1,8 @@ import "@testing-library/jest-dom/vitest"; +import { afterEach } from "vitest"; +import { cleanup } from "@testing-library/react"; + +// `@testing-library/react` only auto-registers `cleanup()` when using globals. +afterEach(() => { + cleanup(); +}); diff --git a/packages/liveblocks-react/.eslintrc.cjs b/packages/liveblocks-react/.eslintrc.cjs index 8ea1fbde59b..65fd65bfa9d 100644 --- a/packages/liveblocks-react/.eslintrc.cjs +++ b/packages/liveblocks-react/.eslintrc.cjs @@ -53,6 +53,7 @@ module.exports = { "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/unbound-method": "off", "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/require-await": "off", // Fine in test mocks }, }, ], diff --git a/packages/liveblocks-react/jest.config.cjs b/packages/liveblocks-react/jest.config.cjs deleted file mode 100644 index 5fa0bec03a3..00000000000 --- a/packages/liveblocks-react/jest.config.cjs +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("@liveblocks/jest-config"); diff --git a/packages/liveblocks-react/package.json b/packages/liveblocks-react/package.json index c8c9eee631e..44fb8c37a97 100644 --- a/packages/liveblocks-react/package.json +++ b/packages/liveblocks-react/package.json @@ -55,10 +55,10 @@ "format": "(eslint --fix src/ || true) && prettier --write src/", "lint": "eslint src/", "lint:package": "publint --strict && attw --pack && bun scripts/check-exports.ts", - "test": "NODE_OPTIONS=\"--no-deprecation\" jest --silent --verbose --color=always", - "test:ci": "NODE_OPTIONS=\"--no-deprecation\" jest --silent --verbose --color=always", + "test": "vitest run", + "test:ci": "vitest run", "test:types": "vitest run --config ./vitest.config.typecheck.ts", - "test:watch": "NODE_OPTIONS=\"--no-deprecation\" jest --silent --verbose --color=always --watch", + "test:watch": "vitest", "test:deps": "depcruise src --exclude __tests__", "showdeps": "depcruise src --include-only '^src' --exclude='__tests__' --output-type dot | dot -T svg > /tmp/dependency-graph.svg && open /tmp/dependency-graph.svg" }, @@ -81,15 +81,14 @@ }, "devDependencies": { "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", "@liveblocks/vitest-config": "*", "@liveblocks/query-parser": "^0.1.1", - "@testing-library/jest-dom": "6.4.6", - "@testing-library/react": "14.1.2", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^16.3.0", "date-fns": "^3.6.0", "eslint-plugin-react-hooks": "^4.6.2", "itertools": "^2.3.2", - "msw": "^1.3.5", + "msw": "^2.10.4", "react-error-boundary": "^4.0.13" }, "sideEffects": false, diff --git a/packages/liveblocks-react/src/__tests__/PaginatedResource.test.ts b/packages/liveblocks-react/src/__tests__/PaginatedResource.test.ts index ceeefb3026c..1bbd8371dae 100644 --- a/packages/liveblocks-react/src/__tests__/PaginatedResource.test.ts +++ b/packages/liveblocks-react/src/__tests__/PaginatedResource.test.ts @@ -1,8 +1,10 @@ +import { describe, expect, test, vi } from "vitest"; + import { PaginatedResource } from "../umbrella-store"; function makeFetcher() { - return jest - .fn, [cursor?: string]>() + return vi + .fn<(cursor?: string) => Promise>() .mockImplementation((cursor?: string) => { const nextCursor = cursor === undefined ? "two" : cursor === "two" ? "three" : null; @@ -12,8 +14,8 @@ function makeFetcher() { function makeUnreliableFetcher() { let i = 0; - return jest - .fn, [cursor?: string]>() + return vi + .fn<(cursor?: string) => Promise>() .mockImplementation(async (cursor?: string) => { if (++i % 2 === 0) { throw new Error("Crap"); @@ -26,14 +28,11 @@ function makeUnreliableFetcher() { } function makeBrokenFetcher() { - return ( - jest - .fn, [cursor?: string]>() - // eslint-disable-next-line @typescript-eslint/require-await - .mockImplementation(async () => { - throw new Error("Crap"); - }) - ); + return vi + .fn<(cursor?: string) => Promise>() + .mockImplementation(async () => { + throw new Error("Crap"); + }); } describe("PaginatedResource", () => { @@ -241,7 +240,7 @@ describe("PaginatedResource", () => { const p = new PaginatedResource(brokenFetcher); expect(p.get()).toEqual({ isLoading: true }); - jest.useFakeTimers(); + vi.useFakeTimers(); try { // Kick the fetcher off const w$ = p.waitUntilLoaded(); @@ -249,19 +248,19 @@ describe("PaginatedResource", () => { expect(brokenFetcher).toHaveBeenCalledTimes(1); expect(p.get()).toEqual({ isLoading: true }); - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); expect(brokenFetcher).toHaveBeenCalledTimes(2); expect(p.get()).toEqual({ isLoading: true }); - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); expect(brokenFetcher).toHaveBeenCalledTimes(3); expect(p.get()).toEqual({ isLoading: true }); - await jest.advanceTimersByTimeAsync(10_000); + await vi.advanceTimersByTimeAsync(10_000); expect(brokenFetcher).toHaveBeenCalledTimes(4); expect(p.get()).toEqual({ isLoading: true }); - await jest.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(15_000); expect(brokenFetcher).toHaveBeenCalledTimes(5); expect(p.get()).toEqual({ isLoading: false, @@ -274,33 +273,33 @@ describe("PaginatedResource", () => { // Referential equality is maintained! expect(p.get() === p.get()).toEqual(true); } finally { - jest.useRealTimers(); + vi.useRealTimers(); } }); test("autoRetry: false — single attempt, error persists (no 5s reset)", async () => { - const fetcher = jest - .fn, [cursor?: string]>() + const fetcher = vi + .fn<(cursor?: string) => Promise>() .mockImplementation(() => { throw new Error("permanent"); }); const p = new PaginatedResource(fetcher, { autoRetry: false }); - jest.useFakeTimers(); + vi.useFakeTimers(); try { const w$ = p.waitUntilLoaded(); await expect(w$).rejects.toThrow("permanent"); expect(fetcher).toHaveBeenCalledTimes(1); - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); expect(fetcher).toHaveBeenCalledTimes(1); expect(p.get()).toEqual({ isLoading: false, error: expect.objectContaining({ message: "permanent" }), }); } finally { - jest.useRealTimers(); + vi.useRealTimers(); } }); }); diff --git a/packages/liveblocks-react/src/__tests__/ThreadDB.test.ts b/packages/liveblocks-react/src/__tests__/ThreadDB.test.ts index b250d086c2e..cffdcc3431a 100644 --- a/packages/liveblocks-react/src/__tests__/ThreadDB.test.ts +++ b/packages/liveblocks-react/src/__tests__/ThreadDB.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, test, vi } from "vitest"; + import { ThreadDB } from "../ThreadDB"; import { dummyThreadData } from "./_dummies"; @@ -116,7 +118,7 @@ describe("ThreadDB", () => { }); test("upsert if newer", () => { - const fn = jest.fn(); + const fn = vi.fn(); const db = new ThreadDB(); const unsub = db.signal.subscribe(fn); @@ -148,7 +150,7 @@ describe("ThreadDB", () => { }); test("upsert should never overwrite already-deleted threads", () => { - const fn = jest.fn(); + const fn = vi.fn(); const db = new ThreadDB(); const unsub = db.signal.subscribe(fn); @@ -179,7 +181,7 @@ describe("ThreadDB", () => { }); test("upsert if newer should never update deleted threads", () => { - const fn = jest.fn(); + const fn = vi.fn(); const db = new ThreadDB(); const unsub = db.signal.subscribe(fn); diff --git a/packages/liveblocks-react/src/__tests__/_MockWebSocket.ts b/packages/liveblocks-react/src/__tests__/_MockWebSocket.ts index 5cbe9e0eb96..e91c971aaae 100644 --- a/packages/liveblocks-react/src/__tests__/_MockWebSocket.ts +++ b/packages/liveblocks-react/src/__tests__/_MockWebSocket.ts @@ -6,8 +6,7 @@ import type { ServerMsg, } from "@liveblocks/core"; import { CrdtType, ServerMsgCode, wait } from "@liveblocks/core"; - -import { waitFor } from "./_utils"; +import { expect, vi } from "vitest"; /** * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code @@ -121,7 +120,7 @@ function remove(array: T[], item: T) { } export async function waitForSocketToBeConnected() { - await waitFor(() => expect(MockWebSocket.instances.length).toBe(1)); + await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1)); const socket = MockWebSocket.instances[0]!; expect(socket.callbacks.open).toEqual([expect.any(Function)]); // Got open callback diff --git a/packages/liveblocks-react/src/__tests__/_restMocks.ts b/packages/liveblocks-react/src/__tests__/_restMocks.ts index 22bdf3c26ea..982fc515847 100644 --- a/packages/liveblocks-react/src/__tests__/_restMocks.ts +++ b/packages/liveblocks-react/src/__tests__/_restMocks.ts @@ -1,5 +1,6 @@ import type { BaseMetadata, + CommentBody, CommentData, GroupData, InboxNotificationData, @@ -11,15 +12,15 @@ import type { ThreadData, ThreadDataWithDeleteInfo, } from "@liveblocks/core"; -import type { ResponseResolver, RestContext, RestRequest } from "msw"; -import { rest } from "msw"; +import type { HttpResponseResolver } from "msw"; +import { http } from "msw"; export function mockGetThreads( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + never, { - data: ThreadData[]; + data: ThreadData[]; inboxNotifications: InboxNotificationData[]; subscriptions: SubscriptionData[]; meta: { @@ -30,7 +31,7 @@ export function mockGetThreads( } > ) { - return rest.get( + return http.get( "https://api.liveblocks.io/v2/c/rooms/:roomId/threads", resolver ); @@ -38,30 +39,37 @@ export function mockGetThreads( export function mockGetThread( params: { threadId: string }, - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + never, { - thread: ThreadData; + thread: ThreadData; inboxNotification?: InboxNotificationData; subscription?: SubscriptionData; } > ) { - return rest.get( + return http.get( `https://api.liveblocks.io/v2/c/rooms/:roomId/thread-with-notification/${params.threadId}`, resolver ); } -export function mockCreateThread( - resolver: ResponseResolver< - RestRequest, - RestContext, - ThreadData +export function mockCreateThread< + TM extends BaseMetadata, + CM extends BaseMetadata, +>( + resolver: HttpResponseResolver< + { roomId: string }, + { + id: string; + metadata?: TM; + comment: { id: string; body: CommentBody; metadata?: CM }; + }, + ThreadData > ) { - return rest.post( + return http.post( "https://api.liveblocks.io/v2/c/rooms/:roomId/threads", resolver ); @@ -69,23 +77,23 @@ export function mockCreateThread( export function mockDeleteThread( params: { threadId: string }, - resolver: ResponseResolver, RestContext, any> + resolver: HttpResponseResolver<{ roomId: string }> ) { - return rest.delete( + return http.delete( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}`, resolver ); } -export function mockCreateComment( +export function mockCreateComment( params: { threadId: string }, - resolver: ResponseResolver< - RestRequest, - RestContext, - CommentData + resolver: HttpResponseResolver< + { roomId: string }, + { id: string; body: CommentBody; metadata?: CM }, + CommentData > ) { - return rest.post( + return http.post( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}/comments`, resolver ); @@ -93,13 +101,13 @@ export function mockCreateComment( export function mockEditComment( params: { threadId: string; commentId: string }, - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + CommentData, CommentData > ) { - return rest.post( + return http.post( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}/comments/${params.commentId}`, resolver ); @@ -107,9 +115,9 @@ export function mockEditComment( export function mockDeleteComment( params: { threadId: string; commentId: string }, - resolver: ResponseResolver, RestContext, any> + resolver: HttpResponseResolver<{ roomId: string }> ) { - return rest.delete( + return http.delete( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}/comments/${params.commentId}`, resolver ); @@ -117,9 +125,9 @@ export function mockDeleteComment( export function mockEditThreadMetadata( params: { threadId: string }, - resolver: ResponseResolver, RestContext, TM> + resolver: HttpResponseResolver<{ roomId: string }, TM, TM> ) { - return rest.post( + return http.post( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}/metadata`, resolver ); @@ -127,9 +135,9 @@ export function mockEditThreadMetadata( export function mockEditCommentMetadata( params: { threadId: string; commentId: string }, - resolver: ResponseResolver, RestContext, CM> + resolver: HttpResponseResolver<{ roomId: string }, CM, CM> ) { - return rest.post( + return http.post( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}/comments/${params.commentId}/metadata`, resolver ); @@ -137,9 +145,9 @@ export function mockEditCommentMetadata( export function mockMarkThreadAsResolved( params: { threadId: string }, - resolver: ResponseResolver, RestContext> + resolver: HttpResponseResolver<{ roomId: string }> ) { - return rest.post( + return http.post( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}/mark-as-resolved`, resolver ); @@ -147,9 +155,9 @@ export function mockMarkThreadAsResolved( export function mockMarkThreadAsUnresolved( params: { threadId: string }, - resolver: ResponseResolver, RestContext> + resolver: HttpResponseResolver<{ roomId: string }> ) { - return rest.post( + return http.post( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}/mark-as-unresolved`, resolver ); @@ -157,9 +165,9 @@ export function mockMarkThreadAsUnresolved( export function mockSubscribeToThread( params: { threadId: string }, - resolver: ResponseResolver, RestContext> + resolver: HttpResponseResolver<{ roomId: string }> ) { - return rest.post( + return http.post( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}/subscribe`, resolver ); @@ -167,36 +175,36 @@ export function mockSubscribeToThread( export function mockUnsubscribeFromThread( params: { threadId: string }, - resolver: ResponseResolver, RestContext> + resolver: HttpResponseResolver<{ roomId: string }> ) { - return rest.post( + return http.post( `https://api.liveblocks.io/v2/c/rooms/:roomId/threads/${params.threadId}/unsubscribe`, resolver ); } export function mockMarkInboxNotificationsAsRead( - resolver: ResponseResolver, RestContext, any> + resolver: HttpResponseResolver<{ roomId: string }> ) { - return rest.post( + return http.post( "https://api.liveblocks.io/v2/c/rooms/:roomId/inbox-notifications/read", resolver ); } export function mockMarkAllInboxNotificationsAsRead( - resolver: ResponseResolver, RestContext, any> + resolver: HttpResponseResolver ) { - return rest.post( + return http.post( "https://api.liveblocks.io/v2/c/inbox-notifications/read", resolver ); } export function mockGetInboxNotifications( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + never, + never, { threads: ThreadData[]; inboxNotifications: InboxNotificationData[]; @@ -209,31 +217,31 @@ export function mockGetInboxNotifications( } > ) { - return rest.get( + return http.get( "https://api.liveblocks.io/v2/c/inbox-notifications", resolver ); } export function mockGetUnreadInboxNotificationsCount( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + never, + never, { count: number; } > ) { - return rest.get( + return http.get( "https://api.liveblocks.io/v2/c/inbox-notifications/count", resolver ); } export function mockGetInboxNotificationsDelta( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + never, + never, { threads: ThreadData[]; inboxNotifications: InboxNotificationData[]; @@ -243,20 +251,21 @@ export function mockGetInboxNotificationsDelta( deletedSubscriptions: SubscriptionData[]; meta: { requestedAt: string; // ISO date + nextCursor?: string; }; } > ) { - return rest.get( + return http.get( "https://api.liveblocks.io/v2/c/inbox-notifications/delta", resolver ); } export function mockDeleteAllInboxNotifications( - resolver: ResponseResolver, RestContext, any> + resolver: HttpResponseResolver ) { - return rest.delete( + return http.delete( "https://api.liveblocks.io/v2/c/inbox-notifications", resolver ); @@ -264,72 +273,64 @@ export function mockDeleteAllInboxNotifications( export function mockDeleteInboxNotification( params: { inboxNotificationId: string }, - resolver: ResponseResolver, RestContext, any> + resolver: HttpResponseResolver ) { - return rest.delete( + return http.delete( `https://api.liveblocks.io/v2/c/inbox-notifications/${params.inboxNotificationId}`, resolver ); } export function mockGetRoomSubscriptionSettings( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + never, RoomSubscriptionSettings > ) { - return rest.get( + return http.get( "https://api.liveblocks.io/v2/c/rooms/:roomId/subscription-settings", resolver ); } export function mockUpdateRoomSubscriptionSettings( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + never, RoomSubscriptionSettings > ) { - return rest.post( + return http.post( "https://api.liveblocks.io/v2/c/rooms/:roomId/subscription-settings", resolver ); } export function mockGetNotificationSettings( - resolver: ResponseResolver< - RestRequest, - RestContext, - NotificationSettingsPlain - > + resolver: HttpResponseResolver ) { - return rest.get( + return http.get( "https://api.liveblocks.io/v2/c/notification-settings", resolver ); } export function mockUpdateNotificationSettings( - resolver: ResponseResolver< - RestRequest, - RestContext, - PartialNotificationSettings - > + resolver: HttpResponseResolver ) { - return rest.post( + return http.post( "https://api.liveblocks.io/v2/c/notification-settings", resolver ); } export function mockFindGroups( - resolver: ResponseResolver< - RestRequest<{ groupIds: string[] }, never>, - RestContext, + resolver: HttpResponseResolver< + never, + { groupIds: string[] }, { groups: GroupData[] } > ) { - return rest.post("https://api.liveblocks.io/v2/c/groups/find", resolver); + return http.post("https://api.liveblocks.io/v2/c/groups/find", resolver); } diff --git a/packages/liveblocks-react/src/__tests__/_utils.tsx b/packages/liveblocks-react/src/__tests__/_utils.tsx index cc9b8dfabc2..444c0ff3561 100644 --- a/packages/liveblocks-react/src/__tests__/_utils.tsx +++ b/packages/liveblocks-react/src/__tests__/_utils.tsx @@ -8,9 +8,13 @@ import { createClient, LiveList, LiveObject } from "@liveblocks/client"; import { assertNever, isPlainObject } from "@liveblocks/core"; import type { AST } from "@liveblocks/query-parser"; import { QueryParser } from "@liveblocks/query-parser"; -import type { RenderHookResult, RenderOptions } from "@testing-library/react"; +import type { + RenderHookOptions, + RenderHookResult, + RenderOptions, +} from "@testing-library/react"; import { render, renderHook } from "@testing-library/react"; -import type { JSXElementConstructor, ReactElement, ReactNode } from "react"; +import type { PropsWithChildren, ReactElement } from "react"; import { createLiveblocksContext, @@ -21,10 +25,9 @@ import { RoomProvider } from "./_liveblocks.config"; import MockWebSocket from "./_MockWebSocket"; /** - * Testing context for all tests. Sets up a default RoomProvider to wrap all - * tests with. + * The default `RoomProvider` wrapping all tests. */ -export function AllTheProviders(props: { children: ReactNode }) { +export function TestingRoomProvider(props: PropsWithChildren) { return ( context. + * A version of `@testing-library/react`'s `renderHook` which uses + * a default `RoomProvider`. */ -function customRender(ui: ReactElement, options?: RenderOptions) { - return render(ui, { wrapper: AllTheProviders, ...options }); +function customRender(ui: ReactElement, renderOptions?: RenderOptions) { + return render(ui, { + wrapper: TestingRoomProvider, + ...renderOptions, + }); } /** - * Wrapper for rendering hooks that are wrapped in a pre set up - * context. + * A version of `@testing-library/react`'s `renderHook` which uses + * a default `RoomProvider`. */ function customRenderHook( render: (initialProps: Props) => Result, - options?: { - initialProps?: Props; - wrapper?: JSXElementConstructor<{ children: ReactNode }>; - } + options?: RenderHookOptions ): RenderHookResult { - return renderHook(render, { wrapper: AllTheProviders, ...options }); + return renderHook(render, { wrapper: TestingRoomProvider, ...options }); } export function createContextsForTest< diff --git a/packages/liveblocks-react/src/__tests__/index.test.tsx b/packages/liveblocks-react/src/__tests__/index.test.tsx index 09e3bcab363..d1e642ed5fc 100644 --- a/packages/liveblocks-react/src/__tests__/index.test.tsx +++ b/packages/liveblocks-react/src/__tests__/index.test.tsx @@ -1,8 +1,18 @@ import { createClient, shallow } from "@liveblocks/client"; import { ClientMsgCode, ServerMsgCode, wait } from "@liveblocks/core"; import { render } from "@testing-library/react"; -import { rest } from "msw"; +import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { createRoomContext, useRoom as useRoomGlobal } from "../room"; import { @@ -25,19 +35,17 @@ const exampleToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NjQ1NjY0MTAsImV4cCI6MTY2NDU3MDAxMCwicGlkIjoiNjA1YTRmZDMxYTM2ZDVlYTdhMmUwOGYxIiwidWlkIjoidXNlcjEiLCJwZXJtcyI6eyIqIjpbInJvb206d3JpdGUiXX0sImsiOiJhY2MifQ.OwLJdtVzMmIwIGO4gVWEJSng3DaUFsljpFXKE0Jcl1OTSHKCpDqJDkHMkkhgHmpUbBPMMdf8QmYa-4h4tMAikxzZL_tFdWQ-5kr92jOFqXPscDQTk0_GCMhv7R6vFj4YjT-msYVNVPI5M0Jlmm9fU5U_s3ZssEYhQl6AYkZT0XErrFYch8WmCVCIQ3bmFuUg5WDtnGJFiQIuCvLr0RyalJh4aILKPZ7ii_u9Q04__rN5kUhIqh2NaXWqFwsITuKaFwn24PJfBz-GJNX5Jk-tlmfJItkPFuBFp3WY8J9r9m59rJF35W_UxMU1tBNYVYRs8c3pjJKdnBiSUDUjNPvxr"; let requestCount = 0; const server = setupServer( - rest.post("/api/auth", (_, res, ctx) => { - return res( - ctx.json({ - token: - // Append a unique counter in the (unchecked) signature part of the - // JWT token at the end, to make each subsequent request return - // a unique value - `${exampleToken}${requestCount++}`, - }) - ); + http.post("/api/auth", () => { + return HttpResponse.json({ + token: + // Append a unique counter in the (unchecked) signature part of the + // JWT token at the end, to make each subsequent request return + // a unique value + `${exampleToken}${requestCount++}`, + }); }), - rest.post("/api/auth-fail", (_, res, ctx) => { - return res(ctx.status(400)); + http.post("/api/auth-fail", () => { + return HttpResponse.json(null, { status: 400 }); }) ); @@ -53,7 +61,7 @@ afterAll(() => server.close()); describe("RoomProvider", () => { test("autoConnect equals false should not call the auth endpoint", () => { - const authEndpointMock = jest.fn(); + const authEndpointMock = vi.fn(); const client = createClient({ authEndpoint: authEndpointMock, }); @@ -70,7 +78,7 @@ describe("RoomProvider", () => { }); test("autoConnect equals true should call the auth endpoint", () => { - const authEndpointMock = jest.fn(); + const authEndpointMock = vi.fn(); const client = createClient({ authEndpoint: authEndpointMock, }); diff --git a/packages/liveblocks-react/src/__tests__/umbrella-store/addReaction.test.ts b/packages/liveblocks-react/src/__tests__/umbrella-store/addReaction.test.ts index 56d6a7df98f..c8c2ed3ac70 100644 --- a/packages/liveblocks-react/src/__tests__/umbrella-store/addReaction.test.ts +++ b/packages/liveblocks-react/src/__tests__/umbrella-store/addReaction.test.ts @@ -1,8 +1,10 @@ +import { describe, expect, test } from "vitest"; + import { applyAddReaction } from "../../umbrella-store"; import { createComment, createThread } from "./_dummies"; describe("addReaction", () => { - it("should add a new reaction to a comment", () => { + test("should add a new reaction to a comment", () => { const comment = createComment({ createdAt: new Date("2024-01-01") }); const thread = createThread({ id: comment.threadId, @@ -29,7 +31,7 @@ describe("addReaction", () => { expect(updatedThread.updatedAt).toEqual(reaction.createdAt); }); - it("should not update updatedAt if not newer", () => { + test("should not update updatedAt if not newer", () => { const now = new Date(); // updatedAt date is latest date const comment = createComment({ createdAt: new Date("2024-01-01") }); const thread = createThread({ @@ -58,7 +60,7 @@ describe("addReaction", () => { expect(updatedThread.updatedAt).toEqual(now); // Not changed! }); - it("should add a new reaction to a comment with existing reactions", () => { + test("should add a new reaction to a comment with existing reactions", () => { const comment = createComment({ createdAt: new Date("2024-01-01"), reactions: [ @@ -98,7 +100,7 @@ describe("addReaction", () => { expect(updatedThread.updatedAt).toEqual(newReaction.createdAt); }); - it("should not add a duplicate reaction for the same user", () => { + test("should not add a duplicate reaction for the same user", () => { const comment = createComment({ createdAt: new Date("2024-01-01"), reactions: [ @@ -124,7 +126,7 @@ describe("addReaction", () => { expect(updatedThread.comments[0]?.reactions[0]?.users).toHaveLength(1); // No additional user should be added }); - it("should add a new user to an existing reaction", () => { + test("should add a new user to an existing reaction", () => { const comment = createComment({ createdAt: new Date("2024-01-01"), reactions: [ @@ -152,7 +154,7 @@ describe("addReaction", () => { ); }); - it("should not add a reaction to a deleted comment", () => { + test("should not add a reaction to a deleted comment", () => { const comment = createComment({ createdAt: new Date("2024-01-01"), }); diff --git a/packages/liveblocks-react/src/__tests__/umbrella-store/applyOptimisticUpdates_forUserNotificationSettings.test.ts b/packages/liveblocks-react/src/__tests__/umbrella-store/applyOptimisticUpdates_forUserNotificationSettings.test.ts index 14b487c2552..31b5d1c6dea 100644 --- a/packages/liveblocks-react/src/__tests__/umbrella-store/applyOptimisticUpdates_forUserNotificationSettings.test.ts +++ b/packages/liveblocks-react/src/__tests__/umbrella-store/applyOptimisticUpdates_forUserNotificationSettings.test.ts @@ -3,6 +3,7 @@ import type { PartialNotificationSettings, } from "@liveblocks/core"; import { createNotificationSettings, nanoid } from "@liveblocks/core"; +import { describe, expect, test } from "vitest"; import { applyOptimisticUpdates_forNotificationSettings } from "../../umbrella-store"; @@ -30,7 +31,7 @@ describe("applyOptimisticUpdates_forNotificationSettings", () => { }, }); - it("should return the same object when no updates are provided", () => { + test("should return the same object when no updates are provided", () => { const result = applyOptimisticUpdates_forNotificationSettings( defaultSettings, [ @@ -44,7 +45,7 @@ describe("applyOptimisticUpdates_forNotificationSettings", () => { expect(result).toEqual(defaultSettings); }); - it("should update a single property in a single channel", () => { + test("should update a single property in a single channel", () => { const updates: PartialNotificationSettings = { email: { thread: true }, }; @@ -66,7 +67,7 @@ describe("applyOptimisticUpdates_forNotificationSettings", () => { expect(result).not.toBe(defaultSettings); // Check immutability }); - it("should update multiple properties in a single channel", () => { + test("should update multiple properties in a single channel", () => { const updates: PartialNotificationSettings = { email: { thread: true, @@ -90,7 +91,7 @@ describe("applyOptimisticUpdates_forNotificationSettings", () => { expect(result.email!.$fileUploaded).toBe(true); }); - it("should update multiple channels simultaneously", () => { + test("should update multiple channels simultaneously", () => { const updates: PartialNotificationSettings = { email: { thread: true }, slack: { textMention: false }, @@ -113,7 +114,7 @@ describe("applyOptimisticUpdates_forNotificationSettings", () => { expect(result.teams).toEqual(defaultSettings.teams); }); - it("should ignore undefined values in updates", () => { + test("should ignore undefined values in updates", () => { const updates: PartialNotificationSettings = { email: { thread: true, @@ -138,7 +139,7 @@ describe("applyOptimisticUpdates_forNotificationSettings", () => { expect(result.email!.$fileUploaded).toBe(false); }); - it("should handle empty channel updates", () => { + test("should handle empty channel updates", () => { const updates: PartialNotificationSettings = { email: {}, }; @@ -157,7 +158,7 @@ describe("applyOptimisticUpdates_forNotificationSettings", () => { expect(result).toEqual(defaultSettings); }); - it("should preserve other channels when updating one", () => { + test("should preserve other channels when updating one", () => { const updates: PartialNotificationSettings = { email: { thread: true }, }; @@ -178,7 +179,7 @@ describe("applyOptimisticUpdates_forNotificationSettings", () => { expect(result.teams).toEqual(defaultSettings.teams); }); - it("should handle all boolean combinations", () => { + test("should handle all boolean combinations", () => { const updates: PartialNotificationSettings = { email: { thread: true, diff --git a/packages/liveblocks-react/src/__tests__/umbrella-store/applyThreadDeltaUpdates.test.tsx b/packages/liveblocks-react/src/__tests__/umbrella-store/applyThreadDeltaUpdates.test.tsx index 893f3632e92..6228a122041 100644 --- a/packages/liveblocks-react/src/__tests__/umbrella-store/applyThreadDeltaUpdates.test.tsx +++ b/packages/liveblocks-react/src/__tests__/umbrella-store/applyThreadDeltaUpdates.test.tsx @@ -1,4 +1,5 @@ import type { ThreadData, ThreadDeleteInfo } from "@liveblocks/core"; +import { describe, expect, test, vi } from "vitest"; import { ThreadDB } from "../../ThreadDB"; import { dummyThreadData } from "../_dummies"; @@ -30,7 +31,7 @@ describe("applyThreadDeltaUpdates", () => { deletedAt: new Date("2024-01-02"), }; - it("should add a new thread if it doesn't exist already", () => { + test("should add a new thread if it doesn't exist already", () => { const db = new ThreadDB(); db.applyDelta([thread1], []); @@ -38,7 +39,7 @@ describe("applyThreadDeltaUpdates", () => { expect(db.findMany(undefined, {}, "asc")).toEqual([thread1]); }); - it("should update an existing thread with a newer one", () => { + test("should update an existing thread with a newer one", () => { const thread1Updated: ThreadData = { ...thread1, updatedAt: new Date("2024-01-03"), // A newer date than the original thread1 @@ -56,7 +57,7 @@ describe("applyThreadDeltaUpdates", () => { expect(db.findMany(undefined, {}, "asc")).toEqual([thread1Updated]); }); - it("should mark a thread as deleted if there is deletion info associated with it", () => { + test("should mark a thread as deleted if there is deletion info associated with it", () => { const db = new ThreadDB(); db.upsert(thread1); @@ -74,7 +75,7 @@ describe("applyThreadDeltaUpdates", () => { }); }); - it("should ignore deletion of a non-existing thread", () => { + test("should ignore deletion of a non-existing thread", () => { const db = new ThreadDB(); db.upsert(thread1); // Only thread1 exists @@ -89,7 +90,7 @@ describe("applyThreadDeltaUpdates", () => { expect(db.findMany(undefined, {}, "asc")).toEqual([thread1]); }); - it("should correctly handle a combination of add, update, and delete operations", () => { + test("should correctly handle a combination of add, update, and delete operations", () => { const db = new ThreadDB(); db.upsert(thread1); // Existing thread @@ -110,8 +111,8 @@ describe("applyThreadDeltaUpdates", () => { }); }); - it("should return existing threads unchanged when no updates are provided", () => { - const fn = jest.fn(); + test("should return existing threads unchanged when no updates are provided", () => { + const fn = vi.fn(); const db = new ThreadDB(); const unsub = db.signal.subscribe(fn); diff --git a/packages/liveblocks-react/src/__tests__/umbrella-store/compareInboxNotifications.test.ts b/packages/liveblocks-react/src/__tests__/umbrella-store/compareInboxNotifications.test.ts index 104ed0227ba..b9bac8b9448 100644 --- a/packages/liveblocks-react/src/__tests__/umbrella-store/compareInboxNotifications.test.ts +++ b/packages/liveblocks-react/src/__tests__/umbrella-store/compareInboxNotifications.test.ts @@ -1,4 +1,5 @@ import type { InboxNotificationData } from "@liveblocks/core"; +import { describe, expect, test } from "vitest"; import { compareInboxNotifications } from "../../umbrella-store"; @@ -22,7 +23,7 @@ describe("compareInboxNotifications", () => { }; // Test case 1: A is newer based on notifiedAt - it("should return 1 if A is newer based on notifiedAt", () => { + test("should return 1 if A is newer based on notifiedAt", () => { inboxNotificationA.notifiedAt = new Date("2024-01-02"); inboxNotificationB.notifiedAt = new Date("2024-01-01"); expect( @@ -31,7 +32,7 @@ describe("compareInboxNotifications", () => { }); // Test case 2: B is newer based on notifiedAt - it("should return -1 if B is newer based on notifiedAt", () => { + test("should return -1 if B is newer based on notifiedAt", () => { inboxNotificationA.notifiedAt = new Date("2024-01-01"); inboxNotificationB.notifiedAt = new Date("2024-01-02"); expect( @@ -40,7 +41,7 @@ describe("compareInboxNotifications", () => { }); // Test case 3: A and B are the same based on notifiedAt, A is read later - it("should return 1 if A and B have the same notifiedAt but A is read later", () => { + test("should return 1 if A and B have the same notifiedAt but A is read later", () => { inboxNotificationA.notifiedAt = new Date("2024-01-01"); inboxNotificationB.notifiedAt = new Date("2024-01-01"); @@ -52,7 +53,7 @@ describe("compareInboxNotifications", () => { }); // Test case 4: A is read, B is unread, same notifiedAt - it("should return 1 if A is read and B is unread with the same notifiedAt", () => { + test("should return 1 if A is read and B is unread with the same notifiedAt", () => { inboxNotificationA.notifiedAt = new Date("2024-01-01"); inboxNotificationB.notifiedAt = new Date("2024-01-01"); @@ -64,7 +65,7 @@ describe("compareInboxNotifications", () => { }); // Test case 5: A is unread, B is read, same notifiedAt - it("should return -1 if A is unread and B is read with the same notifiedAt", () => { + test("should return -1 if A is unread and B is read with the same notifiedAt", () => { inboxNotificationA.notifiedAt = new Date("2024-01-01"); inboxNotificationB.notifiedAt = new Date("2024-01-01"); @@ -76,7 +77,7 @@ describe("compareInboxNotifications", () => { }); // Test case 6: A and B have the same notifiedAt and readAt - it("should return 0 if A and B have the same notifiedAt and readAt", () => { + test("should return 0 if A and B have the same notifiedAt and readAt", () => { inboxNotificationA.notifiedAt = new Date("2024-01-01"); inboxNotificationB.notifiedAt = new Date("2024-01-01"); diff --git a/packages/liveblocks-react/src/__tests__/umbrella-store/deleteComment.test.ts b/packages/liveblocks-react/src/__tests__/umbrella-store/deleteComment.test.ts index 3163656469c..f4232c26c19 100644 --- a/packages/liveblocks-react/src/__tests__/umbrella-store/deleteComment.test.ts +++ b/packages/liveblocks-react/src/__tests__/umbrella-store/deleteComment.test.ts @@ -1,8 +1,10 @@ +import { describe, expect, test } from "vitest"; + import { applyDeleteComment } from "../../umbrella-store"; import { createAttachment, createComment, createThread } from "./_dummies"; describe("deleteComment", () => { - it("should mark a comment as deleted in a thread", () => { + test("should mark a comment as deleted in a thread", () => { const comment = createComment({ createdAt: new Date("2024-01-01") }); const thread = createThread({ @@ -33,7 +35,7 @@ describe("deleteComment", () => { expect(updatedComment.attachments.length).toEqual(0); }); - it("should not delete a comment from a deleted thread", () => { + test("should not delete a comment from a deleted thread", () => { const comment = createComment({ createdAt: new Date("2024-01-01") }); const thread = createThread({ @@ -54,7 +56,7 @@ describe("deleteComment", () => { expect(updatedThread).toEqual(thread); }); - it("should not delete a comment that does not exist", () => { + test("should not delete a comment that does not exist", () => { const thread = createThread({ createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), @@ -73,7 +75,7 @@ describe("deleteComment", () => { expect(updatedThread.comments.length).toBe(1); }); - it("should not delete an already deleted comment", () => { + test("should not delete an already deleted comment", () => { const comment = createComment({ createdAt: new Date("2024-01-01"), deletedAt: new Date("2024-01-02"), @@ -98,7 +100,7 @@ describe("deleteComment", () => { expect(updatedThread.updatedAt).toEqual(thread.updatedAt); // The thread's updatedAt should not change }); - it("should update the thread's updatedAt when deleting the last comment", () => { + test("should update the thread's updatedAt when deleting the last comment", () => { const comment = createComment({ createdAt: new Date("2024-01-01"), deletedAt: new Date("2024-01-02"), diff --git a/packages/liveblocks-react/src/__tests__/umbrella-store/index.test.ts b/packages/liveblocks-react/src/__tests__/umbrella-store/index.test.ts index 1b86c4f6735..66126d80e4d 100644 --- a/packages/liveblocks-react/src/__tests__/umbrella-store/index.test.ts +++ b/packages/liveblocks-react/src/__tests__/umbrella-store/index.test.ts @@ -1,4 +1,5 @@ import { kInternal } from "@liveblocks/core"; +import { describe, expect, test } from "vitest"; import { ThreadDB } from "../../ThreadDB"; import { UmbrellaStore } from "../../umbrella-store"; @@ -27,7 +28,7 @@ const NO_CLIENT = { const LOADING = { isLoading: true }; describe("Umbrella Store", () => { - it("getters returns the expected shapes", () => { + test("getters returns the expected shapes", () => { const store = new UmbrellaStore(NO_CLIENT); // Sync getters @@ -59,7 +60,7 @@ describe("Umbrella Store", () => { ).toEqual(LOADING); }); - it("calling getters multiple times should always return a stable result", () => { + test("calling getters multiple times should always return a stable result", () => { const store = new UmbrellaStore(NO_CLIENT); // IMPORTANT! Strict equality expected! diff --git a/packages/liveblocks-react/src/__tests__/umbrella-store/upsertComment.test.ts b/packages/liveblocks-react/src/__tests__/umbrella-store/upsertComment.test.ts index c606b6f7e0e..39f1c69fdb6 100644 --- a/packages/liveblocks-react/src/__tests__/umbrella-store/upsertComment.test.ts +++ b/packages/liveblocks-react/src/__tests__/umbrella-store/upsertComment.test.ts @@ -1,8 +1,10 @@ +import { describe, expect, test } from "vitest"; + import { applyUpsertComment } from "../../umbrella-store"; import { createComment, createThread } from "./_dummies"; describe("upsertComment", () => { - it("should add a new comment to an empty thread", () => { + test("should add a new comment to an empty thread", () => { const thread = createThread({ createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), @@ -19,7 +21,7 @@ describe("upsertComment", () => { expect(updatedThread.updatedAt).toEqual(comment.createdAt); }); - it("should add a new comment to a thread with existing comments", () => { + test("should add a new comment to a thread with existing comments", () => { const thread = createThread({ comments: [createComment(), createComment()], createdAt: new Date("2024-01-01"), @@ -36,7 +38,7 @@ describe("upsertComment", () => { expect(updatedThread.updatedAt).toEqual(comment.createdAt); }); - it("should update an existing comment", () => { + test("should update an existing comment", () => { const comment = createComment({ createdAt: new Date("2024-01-01") }); const thread = createThread({ id: comment.threadId, @@ -64,7 +66,7 @@ describe("upsertComment", () => { expect(updatedThread.updatedAt).toEqual(new Date("2024-01-02")); }); - it("should not update an existing comment if the new comment is older", () => { + test("should not update an existing comment if the new comment is older", () => { const comment = createComment({ createdAt: new Date("2024-01-01"), editedAt: new Date("2024-01-03"), @@ -93,7 +95,7 @@ describe("upsertComment", () => { expect(updatedThread.updatedAt).toBe(thread.updatedAt); }); - it("should add a new comment if the thread has been updatedAt more recently than the comment creation date", () => { + test("should add a new comment if the thread has been updatedAt more recently than the comment creation date", () => { const thread = createThread({ createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-03"), @@ -110,7 +112,7 @@ describe("upsertComment", () => { expect(updatedThread.updatedAt).toEqual(thread.updatedAt); }); - it("should update a comment if the thread has been updatedAt more recently", () => { + test("should update a comment if the thread has been updatedAt more recently", () => { const comment = createComment({ createdAt: new Date("2024-01-01") }); const thread = createThread({ id: comment.threadId, @@ -136,7 +138,7 @@ describe("upsertComment", () => { expect(updatedThread.updatedAt).toEqual(thread.updatedAt); }); - it("should not update a comment if the thread has been deleted", () => { + test("should not update a comment if the thread has been deleted", () => { const comment = createComment({ createdAt: new Date("2024-01-01") }); const thread = createThread({ id: comment.threadId, @@ -161,7 +163,7 @@ describe("upsertComment", () => { expect(updatedThread.comments).not.toContainEqual(updatedComment); }); - it("should not add a new comment if the thread has been deleted", () => { + test("should not add a new comment if the thread has been deleted", () => { const thread = createThread({ deletedAt: new Date("2024-01-02"), }); diff --git a/packages/liveblocks-react/src/__tests__/useCreateComment.test.tsx b/packages/liveblocks-react/src/__tests__/useCreateComment.test.tsx index b3446f7cecd..65354a4d03e 100644 --- a/packages/liveblocks-react/src/__tests__/useCreateComment.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useCreateComment.test.tsx @@ -1,8 +1,18 @@ -import type { BaseMetadata, CommentBody } from "@liveblocks/core"; -import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { type CommentData, nanoid, Permission } from "@liveblocks/core"; +import { act, renderHook } from "@testing-library/react"; import { addMinutes } from "date-fns"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyCommentData, @@ -36,40 +46,32 @@ describe("useCreateComment", () => { const initialThread = dummyThreadData({ roomId }); server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockCreateComment( - { threadId: initialThread.id }, - async (req, res, ctx) => { - const json = await req.json<{ id: string; body: CommentBody }>(); - - const comment = dummyCommentData({ - roomId, - threadId: initialThread.id, - body: json.body, - createdAt: fakeCreatedAt, - }); - - return res(ctx.json(comment)); - } - ) + mockCreateComment({ threadId: initialThread.id }, async ({ request }) => { + const json = await request.json(); + + const comment = dummyCommentData({ + roomId, + threadId: initialThread.id, + body: json.body, + createdAt: fakeCreatedAt, + }); + + return HttpResponse.json(comment); + }) ); const { @@ -90,24 +92,25 @@ describe("useCreateComment", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); - const comment = await act(() => - result.current.createComment({ + let comment!: CommentData; + act(() => { + comment = result.current.createComment({ threadId: initialThread.id, body: { version: 1, content: [{ type: "paragraph", children: [{ text: "Hello" }] }], }, - }) - ); + }); + }); expect(result.current.threads?.[0]?.comments[1]).toEqual(comment); // We're using the createdDate overriden by the server to ensure the optimistic update have been properly deleted - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads?.[0]?.comments[1]?.createdAt).toEqual( fakeCreatedAt ) @@ -129,41 +132,33 @@ describe("useCreateComment", () => { const fakeCreatedAt = addMinutes(new Date(), 5); server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [initialInboxNotification], - subscriptions: [initialSubscription], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [initialInboxNotification], + subscriptions: [initialSubscription], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockCreateComment( - { threadId: initialThread.id }, - async (req, res, ctx) => { - const json = await req.json<{ id: string; body: CommentBody }>(); - - const comment = dummyCommentData({ - roomId, - id: json.id, - body: json.body, - createdAt: fakeCreatedAt, - threadId: initialThread.id, - }); - - return res(ctx.json(comment)); - } - ) + mockCreateComment({ threadId: initialThread.id }, async ({ request }) => { + const json = await request.json(); + + const comment = dummyCommentData({ + roomId, + id: json.id, + body: json.body, + createdAt: fakeCreatedAt, + threadId: initialThread.id, + }); + + return HttpResponse.json(comment); + }) ); const { @@ -190,25 +185,26 @@ describe("useCreateComment", () => { expect(result.current.subscription.status).toEqual("not-subscribed"); - await waitFor(() => + await vi.waitFor(() => expect(result.current.subscription.unreadSince).toBeNull() ); - const comment = await act(() => - result.current.createComment({ + let comment!: CommentData; + act(() => { + comment = result.current.createComment({ threadId: initialThread.id, body: { version: 1, content: [{ type: "paragraph", children: [{ text: "Hello" }] }], }, - }) - ); + }); + }); expect(result.current.subscription.status).toEqual("subscribed"); expect(result.current.subscription.unreadSince).toEqual(comment.createdAt); // We're using the createdDate overriden by the server to ensure the optimistic update have been properly deleted - await waitFor(() => + await vi.waitFor(() => expect(result.current.subscription.unreadSince).toEqual(fakeCreatedAt) ); @@ -220,30 +216,23 @@ describe("useCreateComment", () => { const initialThread = dummyThreadData({ roomId }); server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), mockCreateComment( { threadId: initialThread.id }, - async (_req, res, ctx) => { - return res(ctx.status(500)); - } + () => new HttpResponse(null, { status: 500 }) ) ); @@ -264,24 +253,25 @@ describe("useCreateComment", () => { ); expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); - const comment = await act(() => - result.current.createComment({ + let comment!: CommentData; + act(() => { + comment = result.current.createComment({ threadId: initialThread.id, body: { version: 1, content: [{ type: "paragraph", children: [{ text: "Hello" }] }], }, - }) - ); + }); + }); expect(result.current.threads?.[0]?.comments[1]).toEqual(comment); // Wait for optimistic update to be rolled back - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -295,45 +285,33 @@ describe("useCreateComment", () => { const metadata = { priority: 1, reviewed: false }; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockCreateComment( - { threadId: initialThread.id }, - async (req, res, ctx) => { - const json = await req.json<{ - id: string; - body: CommentBody; - metadata?: BaseMetadata; - }>(); - - const comment = dummyCommentData({ - roomId, - threadId: initialThread.id, - body: json.body, - createdAt: fakeCreatedAt, - metadata: json.metadata ?? {}, - }); - - return res(ctx.json(comment)); - } - ) + mockCreateComment({ threadId: initialThread.id }, async ({ request }) => { + const json = await request.json(); + + const comment = dummyCommentData({ + roomId, + threadId: initialThread.id, + body: json.body, + createdAt: fakeCreatedAt, + metadata: json.metadata ?? {}, + }); + + return HttpResponse.json(comment); + }) ); const { @@ -354,26 +332,27 @@ describe("useCreateComment", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); - const comment = await act(() => - result.current.createComment({ + let comment!: CommentData; + act(() => { + comment = result.current.createComment({ threadId: initialThread.id, body: { version: 1, content: [{ type: "paragraph", children: [{ text: "Hello" }] }], }, metadata, - }) - ); + }); + }); expect(result.current.threads?.[0]?.comments[1]).toEqual(comment); expect(comment.metadata).toEqual(metadata); // We're using the createdDate overriden by the server to ensure the optimistic update have been properly deleted - await waitFor(() => { + await vi.waitFor(() => { const serverComment = result.current.threads?.[0]?.comments[1]; expect(serverComment?.createdAt).toEqual(fakeCreatedAt); expect(serverComment?.metadata).toEqual(metadata); diff --git a/packages/liveblocks-react/src/__tests__/useCreateThread.test.tsx b/packages/liveblocks-react/src/__tests__/useCreateThread.test.tsx index 94cf25fe889..7a05f16cfaa 100644 --- a/packages/liveblocks-react/src/__tests__/useCreateThread.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useCreateThread.test.tsx @@ -1,9 +1,18 @@ -import type { BaseMetadata, CommentBody, ThreadData } from "@liveblocks/core"; -import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { nanoid, Permission, type ThreadData } from "@liveblocks/core"; +import { act, renderHook } from "@testing-library/react"; import { addMinutes } from "date-fns"; -import type { ResponseComposition, RestContext, RestRequest } from "msw"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyCommentData, dummyThreadData } from "./_dummies"; import MockWebSocket from "./_MockWebSocket"; @@ -31,51 +40,40 @@ describe("useCreateThread", () => { const fakeCreatedAt = addMinutes(new Date(), 5); server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [], - inboxNotifications: [], - subscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockCreateThread( - async ( - req: RestRequest, - res: ResponseComposition>, - ctx: RestContext - ) => { - const json = await req.json<{ - id: string; - comment: { id: string; body: CommentBody }; - }>(); - - const comment = dummyCommentData({ - roomId, - threadId: json.id, - id: json.comment.id, - body: json.comment.body, - createdAt: fakeCreatedAt, - }); - - const thread = dummyThreadData({ - roomId, - id: json.id, - comments: [comment], - createdAt: fakeCreatedAt, - }); - - return res(ctx.json(thread)); - } - ) + mockCreateThread(async ({ request }) => { + const json = await request.json(); + + const comment = dummyCommentData({ + roomId, + threadId: json.id, + id: json.comment.id, + body: json.comment.body, + createdAt: fakeCreatedAt, + }); + + const thread = dummyThreadData({ + roomId, + id: json.id, + comments: [comment], + createdAt: fakeCreatedAt, + }); + + return HttpResponse.json(thread); + }) ); const { @@ -96,7 +94,7 @@ describe("useCreateThread", () => { expect(result.current.threadData).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threadData).toEqual({ isLoading: false, threads: [], @@ -107,19 +105,20 @@ describe("useCreateThread", () => { }) ); - const thread = await act(() => - result.current.createThread({ + let thread!: ThreadData; + act(() => { + thread = result.current.createThread({ body: { version: 1, content: [{ type: "paragraph", children: [{ text: "Hello" }] }], }, - }) - ); + }); + }); expect(result.current.threadData.threads?.[0]).toEqual(thread); // We're using the createdDate overriden by the server to ensure the optimistic update have been properly deleted - await waitFor(() => + await vi.waitFor(() => expect(result.current.threadData.threads?.[0]?.createdAt).toEqual( fakeCreatedAt ) @@ -132,25 +131,21 @@ describe("useCreateThread", () => { const roomId = nanoid(); server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [], - inboxNotifications: [], - subscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockCreateThread((_req, res, ctx) => { - return res(ctx.status(500)); - }) + mockCreateThread(() => new HttpResponse(null, { status: 500 })) ); const { @@ -171,7 +166,7 @@ describe("useCreateThread", () => { expect(result.current.threadsData).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threadsData).toEqual({ isLoading: false, threads: [], @@ -182,19 +177,22 @@ describe("useCreateThread", () => { }) ); - const thread = await act(() => - result.current.createThread({ + let thread!: ThreadData; + act(() => { + thread = result.current.createThread({ body: { version: 1, content: [{ type: "paragraph", children: [{ text: "Hello" }] }], }, - }) - ); + }); + }); expect(result.current.threadsData.threads).toEqual([thread]); // Wait for optimistic update to be rolled back - await waitFor(() => expect(result.current.threadsData.threads).toEqual([])); + await vi.waitFor(() => + expect(result.current.threadsData.threads).toEqual([]) + ); unmount(); }); @@ -205,52 +203,41 @@ describe("useCreateThread", () => { const commentMetadata = { priority: 1, reviewed: false }; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [], - inboxNotifications: [], - subscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockCreateThread( - async ( - req: RestRequest, - res: ResponseComposition>, - ctx: RestContext - ) => { - const json = await req.json<{ - id: string; - comment: { id: string; body: CommentBody; metadata?: BaseMetadata }; - }>(); - - const comment = dummyCommentData({ - roomId, - threadId: json.id, - id: json.comment.id, - body: json.comment.body, - createdAt: fakeCreatedAt, - metadata: json.comment.metadata ?? {}, - }); - - const thread = dummyThreadData({ - roomId, - id: json.id, - comments: [comment], - createdAt: fakeCreatedAt, - }); - - return res(ctx.json(thread)); - } - ) + mockCreateThread(async ({ request }) => { + const json = await request.json(); + + const comment = dummyCommentData({ + roomId, + threadId: json.id, + id: json.comment.id, + body: json.comment.body, + createdAt: fakeCreatedAt, + metadata: json.comment.metadata ?? {}, + }); + + const thread = dummyThreadData({ + roomId, + id: json.id, + comments: [comment], + createdAt: fakeCreatedAt, + }); + + return HttpResponse.json(thread); + }) ); const { @@ -271,7 +258,7 @@ describe("useCreateThread", () => { expect(result.current.threadData).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threadData).toEqual({ isLoading: false, threads: [], @@ -282,21 +269,22 @@ describe("useCreateThread", () => { }) ); - const thread = await act(() => - result.current.createThread({ + let thread!: ThreadData; + act(() => { + thread = result.current.createThread({ body: { version: 1, content: [{ type: "paragraph", children: [{ text: "Hello" }] }], }, commentMetadata, - }) - ); + }); + }); expect(result.current.threadData.threads?.[0]).toEqual(thread); expect(thread.comments[0]?.metadata).toEqual(commentMetadata); // We're using the createdDate overriden by the server to ensure the optimistic update have been properly deleted - await waitFor(() => { + await vi.waitFor(() => { const serverThread = result.current.threadData.threads?.[0]; expect(serverThread?.createdAt).toEqual(fakeCreatedAt); expect(serverThread?.comments[0]?.metadata).toEqual(commentMetadata); diff --git a/packages/liveblocks-react/src/__tests__/useDeleteAllInboxNotifications.test.tsx b/packages/liveblocks-react/src/__tests__/useDeleteAllInboxNotifications.test.tsx index 8d3a8d7dc67..9fb13e2585e 100644 --- a/packages/liveblocks-react/src/__tests__/useDeleteAllInboxNotifications.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useDeleteAllInboxNotifications.test.tsx @@ -1,6 +1,16 @@ import { nanoid } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyCommentData, @@ -49,10 +59,9 @@ describe("useDeleteAllInboxNotifications", () => { ]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -61,10 +70,13 @@ describe("useDeleteAllInboxNotifications", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), - mockDeleteAllInboxNotifications((_req, res, ctx) => res(ctx.status(204))) + }, + { status: 200 } + ); + }), + mockDeleteAllInboxNotifications(() => { + return HttpResponse.json(null, { status: 204 }); + }) ); const { @@ -92,7 +104,7 @@ describe("useDeleteAllInboxNotifications", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -128,10 +140,9 @@ describe("useDeleteAllInboxNotifications", () => { ]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -140,10 +151,13 @@ describe("useDeleteAllInboxNotifications", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), - mockDeleteAllInboxNotifications((_req, res, ctx) => res(ctx.status(500))) + }, + { status: 200 } + ); + }), + mockDeleteAllInboxNotifications(() => { + return HttpResponse.json(null, { status: 500 }); + }) ); const { @@ -166,7 +180,7 @@ describe("useDeleteAllInboxNotifications", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -179,7 +193,7 @@ describe("useDeleteAllInboxNotifications", () => { // We delete the notifications optimitiscally expect(result.current.inboxNotifications).toEqual([]); - await waitFor(() => { + await vi.waitFor(() => { // The optimistic update is reverted because of the error response expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) @@ -214,30 +228,29 @@ describe("useDeleteAllInboxNotifications", () => { ]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - inboxNotifications, - threads, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ) - ), - mockGetUnreadInboxNotificationsCount(async (_req, res, ctx) => { + mockGetInboxNotifications(() => { + return HttpResponse.json({ + inboxNotifications, + threads, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); + }), + mockGetUnreadInboxNotificationsCount(async () => { unreadInboxNotificationsCountCalls++; if (unreadInboxNotificationsCountCalls === 1) { - return res(ctx.json({ count: 2 })); + return HttpResponse.json({ count: 2 }); } else { - return res(ctx.json({ count: 0 })); + return HttpResponse.json({ count: 0 }); } }), - mockDeleteAllInboxNotifications((_req, res, ctx) => res(ctx.status(500))) + mockDeleteAllInboxNotifications(() => { + return HttpResponse.json(null, { status: 500 }); + }) ); const { @@ -262,7 +275,7 @@ describe("useDeleteAllInboxNotifications", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ); @@ -276,7 +289,7 @@ describe("useDeleteAllInboxNotifications", () => { // We delete the notifications optimitiscally expect(result.current.inboxNotifications).toEqual([]); - await waitFor(() => { + await vi.waitFor(() => { // The optimistic update is reverted because of the error response expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) @@ -313,30 +326,29 @@ describe("useDeleteAllInboxNotifications", () => { ]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - inboxNotifications, - threads, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ) - ), - mockGetUnreadInboxNotificationsCount(async (_req, res, ctx) => { + mockGetInboxNotifications(() => { + return HttpResponse.json({ + inboxNotifications, + threads, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); + }), + mockGetUnreadInboxNotificationsCount(async () => { unreadInboxNotificationsCountCalls++; if (unreadInboxNotificationsCountCalls === 1) { - return res(ctx.json({ count: 2 })); + return HttpResponse.json({ count: 2 }); } else { - return res(ctx.json({ count: 0 })); + return HttpResponse.json({ count: 0 }); } }), - mockDeleteAllInboxNotifications((_req, res, ctx) => res(ctx.status(204))) + mockDeleteAllInboxNotifications(() => { + return HttpResponse.json(null, { status: 204 }); + }) ); const { @@ -361,7 +373,7 @@ describe("useDeleteAllInboxNotifications", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ); @@ -375,11 +387,11 @@ describe("useDeleteAllInboxNotifications", () => { // We delete the notifications optimitiscally expect(result.current.inboxNotifications).toEqual([]); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.inboxNotifications).toEqual([]); }); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.unreadInboxNotificationsCount).toEqual(0); }); unmount(); @@ -387,8 +399,11 @@ describe("useDeleteAllInboxNotifications", () => { test("should support deleting all notifications and one if its related thread", async () => { const roomId = nanoid(); - const thread1 = dummyThreadData({ roomId }); - const thread2 = dummyThreadData({ roomId }); + const userId = "userId"; + const comment1 = dummyCommentData({ roomId, userId }); + const comment2 = dummyCommentData({ roomId, userId }); + const thread1 = dummyThreadData({ roomId, comments: [comment1] }); + const thread2 = dummyThreadData({ roomId, comments: [comment2] }); const threads = [thread1, thread2]; const notification1 = dummyThreadInboxNotificationData({ roomId, @@ -408,10 +423,9 @@ describe("useDeleteAllInboxNotifications", () => { let hasCalledDeleteThread = false; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -420,13 +434,16 @@ describe("useDeleteAllInboxNotifications", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), - mockDeleteAllInboxNotifications((_req, res, ctx) => res(ctx.status(204))), - mockDeleteThread({ threadId: threads[0]!.id }, async (_req, res, ctx) => { + }, + { status: 200 } + ); + }), + mockDeleteAllInboxNotifications(() => { + return HttpResponse.json(null, { status: 204 }); + }), + mockDeleteThread({ threadId: threads[0]!.id }, () => { hasCalledDeleteThread = true; - return res(ctx.status(204)); + return HttpResponse.json(null, { status: 204 }); }) ); @@ -437,7 +454,7 @@ describe("useDeleteAllInboxNotifications", () => { useInboxNotifications, useDeleteAllInboxNotifications, }, - } = createContextsForTest({ userId: "user-id" }); + } = createContextsForTest({ userId }); const { result, unmount } = renderHook( () => ({ @@ -459,7 +476,7 @@ describe("useDeleteAllInboxNotifications", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -482,7 +499,7 @@ describe("useDeleteAllInboxNotifications", () => { // TODO: We should wait for the `deleteThread` call to be finished but we don't have APIs for that yet // We should expose a way to know (and be updated about) if there are still pending optimistic updates // Until then, we'll just wait for the mock to be called - await waitFor(() => expect(hasCalledDeleteThread).toEqual(true)); + await vi.waitFor(() => expect(hasCalledDeleteThread).toEqual(true)); unmount(); }); @@ -503,10 +520,9 @@ describe("useDeleteAllInboxNotifications", () => { let hasCalledDeleteComment = false; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -515,17 +531,17 @@ describe("useDeleteAllInboxNotifications", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), - mockDeleteAllInboxNotifications((_req, res, ctx) => res(ctx.status(204))), - mockDeleteComment( - { threadId: thread.id, commentId: comment.id }, - async (_req, res, ctx) => { - hasCalledDeleteComment = true; - return res(ctx.status(204)); - } - ) + }, + { status: 200 } + ); + }), + mockDeleteAllInboxNotifications(() => { + return HttpResponse.json(null, { status: 204 }); + }), + mockDeleteComment({ threadId: thread.id, commentId: comment.id }, () => { + hasCalledDeleteComment = true; + return HttpResponse.json(null, { status: 204 }); + }) ); const { @@ -557,7 +573,7 @@ describe("useDeleteAllInboxNotifications", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -583,7 +599,7 @@ describe("useDeleteAllInboxNotifications", () => { // TODO: We should wait for the `deleteComment` call to be finished but we don't have APIs for that yet // We should expose a way to know (and be updated about) if there are still pending optimistic updates // Until then, we'll just wait for the mock to be called - await waitFor(() => expect(hasCalledDeleteComment).toEqual(true)); + await vi.waitFor(() => expect(hasCalledDeleteComment).toEqual(true)); unmount(); }); diff --git a/packages/liveblocks-react/src/__tests__/useDeleteInboxNotification.test.tsx b/packages/liveblocks-react/src/__tests__/useDeleteInboxNotification.test.tsx index 5a003db5855..e0ecf52a422 100644 --- a/packages/liveblocks-react/src/__tests__/useDeleteInboxNotification.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useDeleteInboxNotification.test.tsx @@ -1,6 +1,16 @@ import { nanoid } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyCommentData, @@ -53,10 +63,9 @@ describe("useDeleteInboxNotification", () => { const subscriptions = [subscription1, subscription2]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -65,12 +74,15 @@ describe("useDeleteInboxNotification", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), + }, + { status: 200 } + ); + }), mockDeleteInboxNotification( { inboxNotificationId: notification1.id }, - (_req, res, ctx) => res(ctx.status(204)) + () => { + return HttpResponse.json(null, { status: 204 }); + } ) ); @@ -99,7 +111,7 @@ describe("useDeleteInboxNotification", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ); @@ -139,10 +151,9 @@ describe("useDeleteInboxNotification", () => { const subscriptions = [subscription1, subscription2]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -151,12 +162,15 @@ describe("useDeleteInboxNotification", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), + }, + { status: 200 } + ); + }), mockDeleteInboxNotification( { inboxNotificationId: notification1.id }, - (_req, res, ctx) => res(ctx.status(500)) + () => { + return HttpResponse.json(null, { status: 500 }); + } ) ); @@ -185,7 +199,7 @@ describe("useDeleteInboxNotification", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -198,7 +212,7 @@ describe("useDeleteInboxNotification", () => { expect(result.current.inboxNotifications).toEqual([notification2]); - await waitFor(() => { + await vi.waitFor(() => { // The optimistic update is reverted because of the error response expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) @@ -235,10 +249,9 @@ describe("useDeleteInboxNotification", () => { let unreadInboxNotificationsCountCalls = 0; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -247,19 +260,22 @@ describe("useDeleteInboxNotification", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), + }, + { status: 200 } + ); + }), mockDeleteInboxNotification( { inboxNotificationId: notification1.id }, - (_req, res, ctx) => res(ctx.status(500)) + () => { + return HttpResponse.json(null, { status: 500 }); + } ), - mockGetUnreadInboxNotificationsCount(async (_req, res, ctx) => { + mockGetUnreadInboxNotificationsCount(async () => { unreadInboxNotificationsCountCalls++; if (unreadInboxNotificationsCountCalls === 1) { - return res(ctx.json({ count: 2 })); + return HttpResponse.json({ count: 2 }); } else { - return res(ctx.json({ count: 1 })); + return HttpResponse.json({ count: 1 }); } }) ); @@ -286,7 +302,7 @@ describe("useDeleteInboxNotification", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ); @@ -302,7 +318,7 @@ describe("useDeleteInboxNotification", () => { expect(result.current.unreadInboxNotificationsCount).toEqual(2); - await waitFor(() => { + await vi.waitFor(() => { // The optimistic update is reverted because of the error response expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) @@ -341,31 +357,30 @@ describe("useDeleteInboxNotification", () => { let unreadInboxNotificationsCountCalls = 0; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - inboxNotifications, - threads, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ) - ), + mockGetInboxNotifications(() => { + return HttpResponse.json({ + inboxNotifications, + threads, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); + }), mockDeleteInboxNotification( { inboxNotificationId: notification1.id }, - (_req, res, ctx) => res(ctx.status(204)) + () => { + return HttpResponse.json(null, { status: 204 }); + } ), - mockGetUnreadInboxNotificationsCount(async (_req, res, ctx) => { + mockGetUnreadInboxNotificationsCount(async () => { unreadInboxNotificationsCountCalls++; if (unreadInboxNotificationsCountCalls === 1) { - return res(ctx.json({ count: 2 })); + return HttpResponse.json({ count: 2 }); } else { - return res(ctx.json({ count: 1 })); + return HttpResponse.json({ count: 1 }); } }) ); @@ -397,7 +412,7 @@ describe("useDeleteInboxNotification", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ); @@ -411,7 +426,7 @@ describe("useDeleteInboxNotification", () => { expect(result.current.inboxNotifications).toEqual([notification2]); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.unreadInboxNotificationsCount).toEqual(1); }); @@ -421,8 +436,16 @@ describe("useDeleteInboxNotification", () => { test("should support deleting a notification and its related thread", async () => { const now = new Date(); const roomId = nanoid(); - const thread1 = dummyThreadData({ roomId, createdAt: now, updatedAt: now }); - const thread2 = dummyThreadData({ roomId }); + const userId = "userId"; + const comment1 = dummyCommentData({ roomId, userId }); + const comment2 = dummyCommentData({ roomId, userId }); + const thread1 = dummyThreadData({ + roomId, + comments: [comment1], + createdAt: now, + updatedAt: now, + }); + const thread2 = dummyThreadData({ roomId, comments: [comment2] }); const threads = [thread1, thread2]; const notification1 = dummyThreadInboxNotificationData({ roomId, @@ -444,10 +467,9 @@ describe("useDeleteInboxNotification", () => { const subscriptions = [subscription1, subscription2]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -456,15 +478,18 @@ describe("useDeleteInboxNotification", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), + }, + { status: 200 } + ); + }), mockDeleteInboxNotification( { inboxNotificationId: notification1.id }, - (_req, res, ctx) => res(ctx.status(500)) + () => { + return HttpResponse.json(null, { status: 500 }); + } ), - mockDeleteThread({ threadId: threads[0]!.id }, async (_req, res, ctx) => { - return res(ctx.status(204)); + mockDeleteThread({ threadId: threads[0]!.id }, () => { + return HttpResponse.json(null, { status: 204 }); }) ); @@ -476,7 +501,7 @@ describe("useDeleteInboxNotification", () => { useInboxNotifications, useDeleteInboxNotification, }, - } = createContextsForTest({ userId: "user-id" }); + } = createContextsForTest({ userId }); const { result, unmount } = renderHook( () => ({ @@ -498,7 +523,7 @@ describe("useDeleteInboxNotification", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -518,10 +543,12 @@ describe("useDeleteInboxNotification", () => { expect(result.current.inboxNotifications).toEqual([notification2]); - await waitFor(() => + await vi.waitFor(() => expect(client.getSyncStatus()).toEqual("synchronizing") ); - await waitFor(() => expect(client.getSyncStatus()).toEqual("synchronized")); + await vi.waitFor(() => + expect(client.getSyncStatus()).toEqual("synchronized") + ); unmount(); }); @@ -544,10 +571,9 @@ describe("useDeleteInboxNotification", () => { const subscriptions = [subscription]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -556,19 +582,19 @@ describe("useDeleteInboxNotification", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), + }, + { status: 200 } + ); + }), mockDeleteInboxNotification( { inboxNotificationId: notification.id }, - (_req, res, ctx) => res(ctx.status(500)) - ), - mockDeleteComment( - { threadId: thread.id, commentId: comment.id }, - async (_req, res, ctx) => { - return res(ctx.status(204)); + () => { + return HttpResponse.json(null, { status: 500 }); } - ) + ), + mockDeleteComment({ threadId: thread.id, commentId: comment.id }, () => { + return HttpResponse.json(null, { status: 204 }); + }) ); const { @@ -601,7 +627,7 @@ describe("useDeleteInboxNotification", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -624,10 +650,12 @@ describe("useDeleteInboxNotification", () => { expect(result.current.inboxNotifications).toEqual([]); - await waitFor(() => + await vi.waitFor(() => expect(client.getSyncStatus()).toEqual("synchronizing") ); - await waitFor(() => expect(client.getSyncStatus()).toEqual("synchronized")); + await vi.waitFor(() => + expect(client.getSyncStatus()).toEqual("synchronized") + ); unmount(); }); diff --git a/packages/liveblocks-react/src/__tests__/useDeleteThread.test.tsx b/packages/liveblocks-react/src/__tests__/useDeleteThread.test.tsx index 2aec51e0188..5b64bfeca90 100644 --- a/packages/liveblocks-react/src/__tests__/useDeleteThread.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useDeleteThread.test.tsx @@ -1,6 +1,17 @@ import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyCommentData, dummyThreadData } from "./_dummies"; import MockWebSocket from "./_MockWebSocket"; @@ -45,28 +56,23 @@ describe("useDeleteThread", () => { let hasCalledDeleteThread = false; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockDeleteThread({ threadId: threads[0]!.id }, async (_req, res, ctx) => { + mockDeleteThread({ threadId: threads[0]!.id }, () => { hasCalledDeleteThread = true; - return res(ctx.status(204)); + return HttpResponse.json(null, { status: 204 }); }) ); @@ -88,18 +94,18 @@ describe("useDeleteThread", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => expect(result.current.threads).toEqual(threads)); + await vi.waitFor(() => expect(result.current.threads).toEqual(threads)); act(() => { result.current.deleteThread(threads[0]!.id); }); - await waitFor(() => expect(result.current.threads).toEqual([])); + await vi.waitFor(() => expect(result.current.threads).toEqual([])); // TODO: We should wait for the `deleteThread` call to be finished but we don't have APIs for that yet // We should expose a way to know (and be updated about) if there are still pending optimistic updates // Until then, we'll just wait for the mock to be called - await waitFor(() => expect(hasCalledDeleteThread).toEqual(true)); + await vi.waitFor(() => expect(hasCalledDeleteThread).toEqual(true)); unmount(); }); @@ -109,25 +115,20 @@ describe("useDeleteThread", () => { const threads = createDummyThreads(roomId, userId); server.use( - mockGetThreads(async (_req, res, ctx) => - res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ) - ) + }, + }); + }) // No need to mock delete thread, as it should not be called ); @@ -153,7 +154,7 @@ describe("useDeleteThread", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => expect(result.current.threads).toEqual(threads)); + await vi.waitFor(() => expect(result.current.threads).toEqual(threads)); expect(result.current.room.getSelf()?.id).toEqual("not-the-thread-creator"); @@ -171,7 +172,7 @@ describe("useDeleteThread", () => { "Only the thread creator can delete the thread" ); - await waitFor(() => expect(result.current.threads).toEqual(threads)); + await vi.waitFor(() => expect(result.current.threads).toEqual(threads)); unmount(); }); @@ -181,27 +182,22 @@ describe("useDeleteThread", () => { const threads = createDummyThreads(roomId, userId); server.use( - mockGetThreads(async (_req, res, ctx) => - res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ) - ), - mockDeleteThread({ threadId: threads[0]!.id }, async (_req, res, ctx) => { - return res(ctx.status(500)); + }, + }); + }), + mockDeleteThread({ threadId: threads[0]!.id }, () => { + return HttpResponse.json(null, { status: 500 }); }) ); @@ -223,7 +219,7 @@ describe("useDeleteThread", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => expect(result.current.threads).toEqual(threads)); + await vi.waitFor(() => expect(result.current.threads).toEqual(threads)); act(() => { result.current.deleteThread(threads[0]!.id); @@ -231,7 +227,7 @@ describe("useDeleteThread", () => { expect(result.current.threads).toEqual([]); - await waitFor(() => expect(result.current.threads).toEqual(threads)); + await vi.waitFor(() => expect(result.current.threads).toEqual(threads)); unmount(); }); diff --git a/packages/liveblocks-react/src/__tests__/useEditComment.test.tsx b/packages/liveblocks-react/src/__tests__/useEditComment.test.tsx index 9f785f9cbd4..d6d025980af 100644 --- a/packages/liveblocks-react/src/__tests__/useEditComment.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useEditComment.test.tsx @@ -1,8 +1,19 @@ import type { BaseMetadata, CommentBody, Patchable } from "@liveblocks/core"; import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; import { addMinutes } from "date-fns"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyCommentData, dummyThreadData } from "./_dummies"; import MockWebSocket from "./_MockWebSocket"; @@ -41,33 +52,28 @@ describe("useEditComment", () => { }); server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), mockEditComment( { threadId: initialThread.id, commentId: initialComment.id }, - async (req, res, ctx) => { - const json = await req.json<{ + async ({ request }) => { + const json = (await request.json()) as { body: CommentBody; attachmentIds?: string[]; metadata?: BaseMetadata; - }>(); + }; const editedComment = dummyCommentData({ roomId, @@ -78,7 +84,7 @@ describe("useEditComment", () => { metadata: json.metadata ?? initialComment.metadata, }); - return res(ctx.json(editedComment)); + return HttpResponse.json(editedComment); } ) ); @@ -101,7 +107,7 @@ describe("useEditComment", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -121,7 +127,7 @@ describe("useEditComment", () => { expect(result.current.threads?.[0]?.comments[0]?.body).toEqual(newBody); expect(result.current.threads?.[0]?.comments[0]?.editedAt).toBeDefined(); - await waitFor(() => { + await vi.waitFor(() => { const comment = result.current.threads?.[0]?.comments[0]; expect(comment?.editedAt).toEqual(fakeEditedAt); expect(comment?.body).toEqual(newBody); @@ -143,32 +149,27 @@ describe("useEditComment", () => { }); server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), mockEditComment( { threadId: initialThread.id, commentId: initialComment.id }, - async (req, res, ctx) => { - const json = await req.json<{ + async ({ request }) => { + const json = (await request.json()) as { body: CommentBody; metadata?: BaseMetadata; - }>(); + }; const editedComment = dummyCommentData({ roomId, @@ -179,7 +180,7 @@ describe("useEditComment", () => { metadata: json.metadata ?? initialComment.metadata, }); - return res(ctx.json(editedComment)); + return HttpResponse.json(editedComment); } ) ); @@ -202,7 +203,7 @@ describe("useEditComment", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -228,7 +229,7 @@ describe("useEditComment", () => { }); expect(result.current.threads?.[0]?.comments[0]?.editedAt).toBeDefined(); - await waitFor(() => { + await vi.waitFor(() => { const comment = result.current.threads?.[0]?.comments[0]; expect(comment?.editedAt).toEqual(fakeEditedAt); expect(comment?.body).toEqual(newBody); @@ -251,39 +252,34 @@ describe("useEditComment", () => { }); server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), mockEditComment( { threadId: initialThread.id, commentId: initialComment.id }, - async (req, res, ctx) => { - const json = await req.json<{ + async ({ request }) => { + const json = (await request.json()) as { body: CommentBody; metadata?: Patchable; - }>(); + }; // Null values = deleted keys const serverMetadata: BaseMetadata = {}; if (json.metadata) { for (const [key, value] of Object.entries(json.metadata)) { if (value !== null) { - serverMetadata[key] = value; + serverMetadata[key] = value as string | number | boolean; } } } @@ -300,7 +296,7 @@ describe("useEditComment", () => { : initialComment.metadata, }); - return res(ctx.json(editedComment)); + return HttpResponse.json(editedComment); } ) ); @@ -323,7 +319,7 @@ describe("useEditComment", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -350,7 +346,7 @@ describe("useEditComment", () => { reviewed: null, }); - await waitFor(() => { + await vi.waitFor(() => { const comment = result.current.threads?.[0]?.comments[0]; expect(comment?.editedAt).toEqual(fakeEditedAt); expect(comment?.body).toEqual(newBody); @@ -375,29 +371,24 @@ describe("useEditComment", () => { }); server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), mockEditComment( { threadId: initialThread.id, commentId: initialComment.id }, - (_req, res, ctx) => { - return res(ctx.status(500)); + () => { + return HttpResponse.json(null, { status: 500 }); } ) ); @@ -420,7 +411,7 @@ describe("useEditComment", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -439,7 +430,7 @@ describe("useEditComment", () => { expect(result.current.threads?.[0]?.comments[0]?.body).toEqual(newBody); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.threads?.[0]?.comments[0]?.body).toEqual( initialComment.body ); diff --git a/packages/liveblocks-react/src/__tests__/useEditCommentMetadata.test.tsx b/packages/liveblocks-react/src/__tests__/useEditCommentMetadata.test.tsx index 19b6492ca67..9762bd222ab 100644 --- a/packages/liveblocks-react/src/__tests__/useEditCommentMetadata.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useEditCommentMetadata.test.tsx @@ -1,6 +1,18 @@ +import type { BaseMetadata } from "@liveblocks/core"; import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyCommentData, dummyThreadData } from "./_dummies"; import MockWebSocket from "./_MockWebSocket"; @@ -36,32 +48,27 @@ describe("useEditCommentMetadata", () => { let hasCalledEditCommentMetadata = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), mockEditCommentMetadata( { threadId: initialThread.id, commentId: initialComment.id }, - async (req, res, ctx) => { + async ({ request }) => { hasCalledEditCommentMetadata = true; - const json = await req.json(); + const json = await request.json(); - return res(ctx.json(json)); + return HttpResponse.json(json); } ) ); @@ -84,7 +91,7 @@ describe("useEditCommentMetadata", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -104,7 +111,7 @@ describe("useEditCommentMetadata", () => { // Comment metadata is not updated by the server response so exceptionally, // we need to check if mock has been called - await waitFor(() => expect(hasCalledEditCommentMetadata).toEqual(true)); + await vi.waitFor(() => expect(hasCalledEditCommentMetadata).toEqual(true)); unmount(); }); @@ -122,34 +129,25 @@ describe("useEditCommentMetadata", () => { let hasCalledEditCommentMetadata = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), mockEditCommentMetadata( { threadId: initialThread.id, commentId: initialComment.id }, - async (_, res, ctx) => { + async () => { hasCalledEditCommentMetadata = true; - return res( - ctx.json({ - priority: 2, - }) - ); + return HttpResponse.json({ priority: 2 }); } ) ); @@ -172,7 +170,7 @@ describe("useEditCommentMetadata", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -195,9 +193,9 @@ describe("useEditCommentMetadata", () => { // Comment metadata is not updated by the server response so exceptionally, // we need to check if mock has been called - await waitFor(() => expect(hasCalledEditCommentMetadata).toEqual(true)); + await vi.waitFor(() => expect(hasCalledEditCommentMetadata).toEqual(true)); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.threads?.[0]?.comments[0]?.metadata).toEqual({ priority: 2, }); diff --git a/packages/liveblocks-react/src/__tests__/useEditThreadMetadata.test.tsx b/packages/liveblocks-react/src/__tests__/useEditThreadMetadata.test.tsx index 57cfae43c7e..fdc2f358a32 100644 --- a/packages/liveblocks-react/src/__tests__/useEditThreadMetadata.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useEditThreadMetadata.test.tsx @@ -1,6 +1,17 @@ import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyThreadData } from "./_dummies"; import MockWebSocket from "./_MockWebSocket"; @@ -29,32 +40,27 @@ describe("useEditThreadMetadata", () => { let hasCalledEditThreadMetadata = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), mockEditThreadMetadata( { threadId: initialThread.id }, - async (req, res, ctx) => { + async ({ request }) => { hasCalledEditThreadMetadata = true; - const json = await req.json(); + const json = await request.json(); - return res(ctx.json(json)); + return HttpResponse.json(json); } ) ); @@ -77,7 +83,7 @@ describe("useEditThreadMetadata", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -94,7 +100,7 @@ describe("useEditThreadMetadata", () => { // Thread updatedAt is not updated by the server response so exceptionally, // we need to check if mock has been called - await waitFor(() => expect(hasCalledEditThreadMetadata).toEqual(true)); + await vi.waitFor(() => expect(hasCalledEditThreadMetadata).toEqual(true)); unmount(); }); @@ -108,34 +114,27 @@ describe("useEditThreadMetadata", () => { let hasCalledEditThreadMetadata = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockEditThreadMetadata( + mockEditThreadMetadata<{ color: string }>( { threadId: initialThread.id }, - async (_, res, ctx) => { + () => { hasCalledEditThreadMetadata = true; - return res( - ctx.json({ - color: "yellow", - }) - ); + return HttpResponse.json({ + color: "yellow", + }); } ) ); @@ -158,7 +157,7 @@ describe("useEditThreadMetadata", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -180,9 +179,9 @@ describe("useEditThreadMetadata", () => { // Thread updatedAt is not updated by the server response so exceptionally, // we need to check if mock has been called - await waitFor(() => expect(hasCalledEditThreadMetadata).toEqual(true)); + await vi.waitFor(() => expect(hasCalledEditThreadMetadata).toEqual(true)); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.threads?.[0]?.metadata).toEqual({ color: "yellow", }); diff --git a/packages/liveblocks-react/src/__tests__/useGroup.test.tsx b/packages/liveblocks-react/src/__tests__/useGroup.test.tsx index dec7a83866c..731f01462a3 100644 --- a/packages/liveblocks-react/src/__tests__/useGroup.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useGroup.test.tsx @@ -1,8 +1,17 @@ -import "@testing-library/jest-dom"; - import { nanoid } from "@liveblocks/core"; -import { renderHook, waitFor } from "@testing-library/react"; +import { renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { useGroup } from "../use-group"; import { dummyGroupData } from "./_dummies"; @@ -13,7 +22,7 @@ import { createContextsForTest } from "./_utils"; const server = setupServer(); beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); server.listen({ onUnhandledRequest: "error" }); }); @@ -27,7 +36,7 @@ afterEach(() => { }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); server.close(); }); @@ -40,24 +49,22 @@ describe("useGroup", () => { } = createContextsForTest(); server.use( - mockFindGroups(async (req, res, ctx) => { - const { groupIds } = await req.json(); - - return res( - ctx.json({ - groups: (groupIds as string[]).map((groupId) => - dummyGroupData({ - id: groupId, - members: [ - { - id: "user-0", - addedAt: new Date(), - }, - ], - }) - ), - }) - ); + mockFindGroups(async ({ request }) => { + const { groupIds } = await request.json(); + + return HttpResponse.json({ + groups: groupIds.map((groupId) => + dummyGroupData({ + id: groupId, + members: [ + { + id: "user-0", + addedAt: new Date(), + }, + ], + }) + ), + }); }) ); @@ -74,7 +81,7 @@ describe("useGroup", () => { expect(result.current.group).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.group.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.group.isLoading).toBeFalsy()); expect(result.current.group).toEqual({ isLoading: false, @@ -106,12 +113,10 @@ describe("useGroup", () => { } = createContextsForTest(); server.use( - mockFindGroups(async (_req, res, ctx) => { - return res( - ctx.json({ - groups: [], - }) - ); + mockFindGroups(async () => { + return HttpResponse.json({ + groups: [], + }); }) ); @@ -128,7 +133,7 @@ describe("useGroup", () => { expect(result.current.group).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.group.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.group.isLoading).toBeFalsy()); expect(result.current.group).toEqual({ isLoading: false, @@ -146,22 +151,20 @@ describe("useGroup", () => { } = createContextsForTest(); server.use( - mockFindGroups(async (_req, res, ctx) => { - return res( - ctx.json({ - groups: [ - dummyGroupData({ - id: "engineering", - members: [ - { - id: "user-0", - addedAt: new Date(), - }, - ], - }), - ], - }) - ); + mockFindGroups(async () => { + return HttpResponse.json({ + groups: [ + dummyGroupData({ + id: "engineering", + members: [ + { + id: "user-0", + addedAt: new Date(), + }, + ], + }), + ], + }); }) ); @@ -179,7 +182,7 @@ describe("useGroup", () => { expect(result.current.group).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.group.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.group.isLoading).toBeFalsy()); expect(result.current.group).toEqual({ isLoading: false, @@ -204,7 +207,7 @@ describe("useGroup", () => { expect(result.current.group).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.group.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.group.isLoading).toBeFalsy()); expect(result.current.group).toEqual({ isLoading: false, @@ -221,29 +224,27 @@ describe("useGroup", () => { room: { RoomProvider }, } = createContextsForTest(); - const mockFindGroupsObserver = jest.fn(); + const mockFindGroupsObserver = vi.fn<(groupIds: string[]) => void>(); server.use( - mockFindGroups(async (req, res, ctx) => { - const { groupIds } = await req.json(); + mockFindGroups(async ({ request }) => { + const { groupIds } = await request.json(); mockFindGroupsObserver(groupIds); - return res( - ctx.json({ - groups: (groupIds as string[]).map((groupId) => - dummyGroupData({ - id: groupId, - members: [ - { - id: "user-0", - addedAt: new Date(), - }, - ], - }) - ), - }) - ); + return HttpResponse.json({ + groups: groupIds.map((groupId) => + dummyGroupData({ + id: groupId, + members: [ + { + id: "user-0", + addedAt: new Date(), + }, + ], + }) + ), + }); }) ); @@ -260,7 +261,7 @@ describe("useGroup", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.groupEngineering.isLoading).toBeFalsy(); expect(result.current.groupEngineering2.isLoading).toBeFalsy(); expect(result.current.groupDesign.isLoading).toBeFalsy(); @@ -337,48 +338,44 @@ describe("useGroup", () => { liveblocks: { LiveblocksProvider, useInboxNotifications }, } = createContextsForTest(); - const mockFindGroupsObserver = jest.fn(); + const mockFindGroupsObserver = vi.fn<(groupIds: string[]) => void>(); server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: [], - inboxNotifications: [], - subscriptions: [], - groups: [ - dummyGroupData({ - id: "engineering", - members: [{ id: "user-0", addedAt: new Date() }], - }), - ], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(async () => { + return HttpResponse.json({ + threads: [], + inboxNotifications: [], + subscriptions: [], + groups: [ + dummyGroupData({ + id: "engineering", + members: [{ id: "user-0", addedAt: new Date() }], + }), + ], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }), - mockFindGroups(async (req, res, ctx) => { - const { groupIds } = await req.json(); + mockFindGroups(async ({ request }) => { + const { groupIds } = await request.json(); mockFindGroupsObserver(groupIds); - return res( - ctx.json({ - groups: (groupIds as string[]).map((groupId) => - dummyGroupData({ - id: groupId, - members: [ - { - id: "user-0", - addedAt: new Date(), - }, - ], - }) - ), - }) - ); + return HttpResponse.json({ + groups: groupIds.map((groupId) => + dummyGroupData({ + id: groupId, + members: [ + { + id: "user-0", + addedAt: new Date(), + }, + ], + }) + ), + }); }) ); @@ -390,7 +387,7 @@ describe("useGroup", () => { ), }); - await waitFor(() => + await vi.waitFor(() => expect(_useInboxNotifications.result.current).toEqual( expect.objectContaining({ isLoading: false, @@ -410,7 +407,7 @@ describe("useGroup", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(_useGroup.result.current.groupEngineering.isLoading).toBeFalsy(); expect(_useGroup.result.current.groupDesign.isLoading).toBeFalsy(); }); diff --git a/packages/liveblocks-react/src/__tests__/useGroupInfo.test.tsx b/packages/liveblocks-react/src/__tests__/useGroupInfo.test.tsx index 885c7a16868..7739a0ceddf 100644 --- a/packages/liveblocks-react/src/__tests__/useGroupInfo.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useGroupInfo.test.tsx @@ -1,14 +1,12 @@ -import "@testing-library/jest-dom"; - import type { ResolveGroupsInfoArgs } from "@liveblocks/core"; import { nanoid } from "@liveblocks/core"; -import { renderHook, screen, waitFor } from "@testing-library/react"; +import { renderHook, screen } from "@testing-library/react"; import { Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { afterAll, beforeAll, describe, expect, test, vi } from "vitest"; import { act, createContextsForTest } from "./_utils"; -// eslint-disable-next-line @typescript-eslint/require-await async function defaultResolveGroupsInfo({ groupIds }: ResolveGroupsInfoArgs) { return groupIds.map((groupId) => ({ name: groupId, @@ -17,11 +15,11 @@ async function defaultResolveGroupsInfo({ groupIds }: ResolveGroupsInfoArgs) { describe("useGroupInfo", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should return an error if resolveGroupsInfo is not set", async () => { @@ -46,7 +44,9 @@ describe("useGroupInfo", () => { expect(result.current.groupInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); expect(result.current.groupInfo).toEqual({ isLoading: false, @@ -80,7 +80,9 @@ describe("useGroupInfo", () => { expect(result.current.groupInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); expect(result.current.groupInfo).toEqual({ isLoading: false, @@ -113,7 +115,9 @@ describe("useGroupInfo", () => { expect(result.current.groupInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); expect(result.current.groupInfo).toEqual({ isLoading: false, @@ -124,7 +128,9 @@ describe("useGroupInfo", () => { expect(result.current.groupInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); expect(result.current.groupInfo).toEqual({ isLoading: false, @@ -137,7 +143,7 @@ describe("useGroupInfo", () => { test("should cache results based on group ID", async () => { const roomId = nanoid(); - const resolveGroupsInfo = jest.fn(({ groupIds }: ResolveGroupsInfoArgs) => + const resolveGroupsInfo = vi.fn(({ groupIds }: ResolveGroupsInfoArgs) => groupIds.map((groupId) => ({ name: groupId })) ); const { @@ -158,11 +164,15 @@ describe("useGroupInfo", () => { } ); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); rerender({ groupId: "123" }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); rerender({ groupId: "abc" }); @@ -183,7 +193,7 @@ describe("useGroupInfo", () => { test("should revalidate instantly if its cache is invalidated", async () => { const roomId = nanoid(); - const resolveGroupsInfo = jest.fn(({ groupIds }: ResolveGroupsInfoArgs) => + const resolveGroupsInfo = vi.fn(({ groupIds }: ResolveGroupsInfoArgs) => groupIds.map((groupId) => ({ name: groupId })) ); const { @@ -205,11 +215,15 @@ describe("useGroupInfo", () => { } ); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); rerender({ groupId: "123" }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); rerender({ groupId: "abc" }); @@ -221,7 +235,9 @@ describe("useGroupInfo", () => { // Invalidate all group IDs act(() => client.resolvers.invalidateGroupsInfo()); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); expect(result.current.groupInfo).toEqual({ isLoading: false, @@ -242,7 +258,7 @@ describe("useGroupInfo", () => { test("should batch (and deduplicate) requests for the same group ID", async () => { const roomId = nanoid(); - const resolveGroupsInfo = jest.fn(({ groupIds }: ResolveGroupsInfoArgs) => + const resolveGroupsInfo = vi.fn(({ groupIds }: ResolveGroupsInfoArgs) => groupIds.map((groupId) => ({ name: groupId })) ); const { @@ -264,7 +280,7 @@ describe("useGroupInfo", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.groupInfoAbc.isLoading).toBeFalsy(); expect(result.current.groupInfoAbc2.isLoading).toBeFalsy(); expect(result.current.groupInfo123.isLoading).toBeFalsy(); @@ -317,7 +333,9 @@ describe("useGroupInfo", () => { expect(result.current.groupInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); expect(result.current.groupInfo).toEqual({ isLoading: false, @@ -350,7 +368,9 @@ describe("useGroupInfo", () => { expect(result.current.groupInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); expect(result.current.groupInfo).toEqual({ isLoading: false, @@ -383,7 +403,9 @@ describe("useGroupInfo", () => { expect(result.current.groupInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); expect(result.current.groupInfo).toEqual({ isLoading: false, @@ -398,7 +420,7 @@ describe("useGroupInfo", () => { test("should return an error if resolveGroupsInfo returns undefined for a specifc group ID", async () => { const roomId = nanoid(); - const resolveGroupsInfo = jest.fn(({ groupIds }: ResolveGroupsInfoArgs) => + const resolveGroupsInfo = vi.fn(({ groupIds }: ResolveGroupsInfoArgs) => groupIds.map((groupId) => { if (groupId === "abc") { return undefined; @@ -424,7 +446,7 @@ describe("useGroupInfo", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.groupInfoAbc.isLoading).toBeFalsy(); expect(result.current.groupInfo123.isLoading).toBeFalsy(); }); @@ -449,11 +471,11 @@ describe("useGroupInfo", () => { describe("useGroupInfoSuspense", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should suspend with Suspense", async () => { @@ -484,14 +506,16 @@ describe("useGroupInfoSuspense", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is displayed expect(screen.getByText("Loading")).toBeInTheDocument(); }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is no longer displayed expect(screen.getByText("Loaded")).toBeInTheDocument(); }); @@ -528,28 +552,32 @@ describe("useGroupInfoSuspense", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is displayed expect(screen.getByText("Loading")).toBeInTheDocument(); }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is no longer displayed expect(screen.getByText("Loaded")).toBeInTheDocument(); }); act(() => client.resolvers.invalidateGroupsInfo()); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is displayed again expect(screen.getByText("Loading")).toBeInTheDocument(); }); - await waitFor(() => expect(result.current.groupInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.groupInfo.isLoading).toBeFalsy() + ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is no longer displayed again expect(screen.getByText("Loaded")).toBeInTheDocument(); }); @@ -590,7 +618,7 @@ describe("useGroupInfoSuspense", () => { expect(result.current).toEqual(null); - await waitFor(() => { + await vi.waitFor(() => { // Check if the error boundary fallback is displayed expect( screen.getByText("There was an error while getting group info.") diff --git a/packages/liveblocks-react/src/__tests__/useHistoryVersions.test.tsx b/packages/liveblocks-react/src/__tests__/useHistoryVersions.test.tsx index 20dce92c23b..d8730d1f9e0 100644 --- a/packages/liveblocks-react/src/__tests__/useHistoryVersions.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useHistoryVersions.test.tsx @@ -1,13 +1,21 @@ -import "@testing-library/jest-dom"; - import type { HistoryVersion } from "@liveblocks/core"; import { nanoid } from "@liveblocks/core"; -import { fireEvent, renderHook, screen, waitFor } from "@testing-library/react"; -import type { ResponseResolver, RestContext, RestRequest } from "msw"; -import { rest } from "msw"; +import { fireEvent, renderHook, screen } from "@testing-library/react"; +import type { HttpResponseResolver } from "msw"; +import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import MockWebSocket from "./_MockWebSocket"; import { createContextsForTest } from "./_utils"; @@ -23,16 +31,16 @@ beforeEach(() => { afterEach(() => { MockWebSocket.reset(); server.resetHandlers(); - jest.clearAllTimers(); - jest.clearAllMocks(); + vi.clearAllTimers(); + vi.clearAllMocks(); }); afterAll(() => server.close()); function mockListHistoryVersions( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + never, { versions: HistoryVersion[]; meta: { @@ -41,16 +49,16 @@ function mockListHistoryVersions( } > ) { - return rest.get( + return http.get( "https://api.liveblocks.io/v2/c/rooms/:roomId/versions", resolver ); } function mockGetHistoryVersionsSince( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + never, { versions: HistoryVersion[]; meta: { @@ -59,7 +67,7 @@ function mockGetHistoryVersionsSince( } > ) { - return rest.get( + return http.get( "https://api.liveblocks.io/v2/c/rooms/:roomId/versions/delta", resolver ); @@ -67,11 +75,11 @@ function mockGetHistoryVersionsSince( describe("useHistoryVersions", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should fetch room versions on mount", async () => { @@ -91,15 +99,13 @@ describe("useHistoryVersions", () => { ]; server.use( - mockListHistoryVersions((_req, res, ctx) => { - return res( - ctx.json({ - versions, - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + mockListHistoryVersions(() => { + return HttpResponse.json({ + versions, + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -117,7 +123,7 @@ describe("useHistoryVersions", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, versions, @@ -144,15 +150,13 @@ describe("useHistoryVersions", () => { ]; server.use( - mockListHistoryVersions((_req, res, ctx) => { - return res( - ctx.json({ - versions, - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + mockListHistoryVersions(() => { + return HttpResponse.json({ + versions, + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -181,7 +185,7 @@ describe("useHistoryVersions", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, versions, @@ -194,12 +198,12 @@ describe("useHistoryVersions", () => { describe("useHistoryVersions: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should return error if initial fetch throws an error", async () => { @@ -207,9 +211,9 @@ describe("useHistoryVersions: error", () => { const roomId = nanoid(); server.use( - mockListHistoryVersions((_req, res, ctx) => { + mockListHistoryVersions(() => { listHistoryVersionsReqCount++; - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -226,26 +230,26 @@ describe("useHistoryVersions: error", () => { expect(result.current).toEqual({ isLoading: true }); // Wait until all fetch attempts have been done - await jest.advanceTimersToNextTimerAsync(); // fetch attempt 1 + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(2)); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(5)); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current).toEqual({ isLoading: false, error: expect.any(Error), @@ -253,17 +257,17 @@ describe("useHistoryVersions: error", () => { }); // Wait for 5 second for the error to clear - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(6)); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(6)); expect(result.current).toEqual({ isLoading: true, }); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(7)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(7)); // and so on... @@ -273,11 +277,11 @@ describe("useHistoryVersions: error", () => { describe("useHistoryVersions: suspense", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should fetch user threads on render", async () => { @@ -297,15 +301,13 @@ describe("useHistoryVersions: suspense", () => { ]; server.use( - mockListHistoryVersions((_req, res, ctx) => { - return res( - ctx.json({ - versions, - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + mockListHistoryVersions(() => { + return HttpResponse.json({ + versions, + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -325,7 +327,7 @@ describe("useHistoryVersions: suspense", () => { expect(result.current).toEqual(null); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, versions, @@ -338,12 +340,12 @@ describe("useHistoryVersions: suspense", () => { describe("useHistoryVersions: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers server.resetHandlers(); }); @@ -353,9 +355,9 @@ describe("useHistoryVersions: error", () => { const roomId = nanoid(); server.use( - mockListHistoryVersions((_req, res, ctx) => { + mockListHistoryVersions(() => { listHistoryVersionsReqCount++; - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -389,40 +391,40 @@ describe("useHistoryVersions: error", () => { expect(screen.getByText("Loading")).toBeInTheDocument(); // Wait until all fetch attempts have been done - await jest.advanceTimersToNextTimerAsync(); // fetch attempt 1 + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(2)); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(listHistoryVersionsReqCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(listHistoryVersionsReqCount).toBe(5)); // Check if the error boundary's fallback is displayed - await waitFor(() => { + await vi.waitFor(() => { expect( screen.getByText("There was an error while getting threads.") ).toBeInTheDocument(); }); // Wait until the error boundary auto-clears - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // Simulate clicking the retry button fireEvent.click(screen.getByText("Retry")); // The error boundary's fallback should be cleared - await waitFor(() => { + await vi.waitFor(() => { expect(screen.getByText("Loading")).toBeInTheDocument(); }); @@ -432,12 +434,12 @@ describe("useHistoryVersions: error", () => { describe("useHistoryVersions: polling", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); - jest.clearAllTimers(); + vi.useRealTimers(); + vi.clearAllTimers(); server.resetHandlers(); }); test("should poll threads every x seconds", async () => { @@ -459,26 +461,22 @@ describe("useHistoryVersions: polling", () => { let getHistoryVersionsSinceCount = 0; server.use( - mockListHistoryVersions((_req, res, ctx) => { - return res( - ctx.json({ - versions, - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + mockListHistoryVersions(() => { + return HttpResponse.json({ + versions, + meta: { + requestedAt: new Date().toISOString(), + }, + }); }), - mockGetHistoryVersionsSince((_req, res, ctx) => { + mockGetHistoryVersionsSince(() => { getHistoryVersionsSinceCount++; - return res( - ctx.json({ - versions, - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + return HttpResponse.json({ + versions, + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -496,7 +494,7 @@ describe("useHistoryVersions: polling", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, versions, @@ -519,10 +517,10 @@ describe("useHistoryVersions: polling", () => { }); // Wait for the first polling to occur after the initial render - await jest.advanceTimersByTimeAsync(60_000); - await waitFor(() => expect(getHistoryVersionsSinceCount).toBe(1)); + await vi.advanceTimersByTimeAsync(60_000); + await vi.waitFor(() => expect(getHistoryVersionsSinceCount).toBe(1)); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, versions, diff --git a/packages/liveblocks-react/src/__tests__/useInboxNotificationThread.test.tsx b/packages/liveblocks-react/src/__tests__/useInboxNotificationThread.test.tsx index 619dc550e86..ecc4826d4e7 100644 --- a/packages/liveblocks-react/src/__tests__/useInboxNotificationThread.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useInboxNotificationThread.test.tsx @@ -1,9 +1,18 @@ -import "@testing-library/jest-dom"; - import { nanoid } from "@liveblocks/core"; -import { renderHook, waitFor } from "@testing-library/react"; +import { renderHook } from "@testing-library/react"; import { sorted } from "itertools"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyCustomInboxNoficationData, @@ -41,19 +50,17 @@ describe("useInboxNotificationThread", () => { const inboxNotifications = [inboxNotification]; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions: [], - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(() => { + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions: [], + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); @@ -75,7 +82,7 @@ describe("useInboxNotificationThread", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications, @@ -117,19 +124,17 @@ describe("useInboxNotificationThread", () => { const inboxNotifications = [inboxNotification, customInboxNotification]; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: [], // NOTE! Not setting the thread ID, making it a broken reference from the inbox notification - inboxNotifications, - subscriptions: [], - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(() => { + return HttpResponse.json({ + threads: [], // NOTE! Not setting the thread ID, making it a broken reference from the inbox notification + inboxNotifications, + subscriptions: [], + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); @@ -160,7 +165,7 @@ describe("useInboxNotificationThread", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: expect.any(Array), @@ -225,19 +230,17 @@ describe("useInboxNotificationThread", () => { const inboxNotifications = [inboxNotification, customInboxNotification]; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(() => { + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); @@ -268,7 +271,7 @@ describe("useInboxNotificationThread", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: expect.any(Array), diff --git a/packages/liveblocks-react/src/__tests__/useInboxNotifications.test.tsx b/packages/liveblocks-react/src/__tests__/useInboxNotifications.test.tsx index 43090ad4914..3842766105f 100644 --- a/packages/liveblocks-react/src/__tests__/useInboxNotifications.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useInboxNotifications.test.tsx @@ -1,5 +1,3 @@ -import "@testing-library/jest-dom"; - import { batch, HttpError, nanoid, wait } from "@liveblocks/core"; import { act, @@ -7,11 +5,21 @@ import { render, renderHook, screen, - waitFor, } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { Suspense, useEffect } from "react"; import { ErrorBoundary, type FallbackProps } from "react-error-boundary"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummySubscriptionData, @@ -52,19 +60,17 @@ describe("useInboxNotifications", () => { ]; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(() => { + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); @@ -82,7 +88,7 @@ describe("useInboxNotifications", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications, @@ -107,34 +113,30 @@ describe("useInboxNotifications", () => { ]; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(() => { + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }), - mockGetInboxNotificationsDelta(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: [], - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + mockGetInboxNotificationsDelta(() => { + return HttpResponse.json({ + threads: [], + inboxNotifications: [], + subscriptions, + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -151,7 +153,7 @@ describe("useInboxNotifications", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications, @@ -182,38 +184,34 @@ describe("useInboxNotifications", () => { ]; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(() => { + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); let getInboxNotificationsReqCount = 0; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { + mockGetInboxNotifications(() => { getInboxNotificationsReqCount++; - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); @@ -234,7 +232,7 @@ describe("useInboxNotifications", () => { } ); - await waitFor(() => expect(getInboxNotificationsReqCount).toBe(1)); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(1)); rerender(); @@ -245,8 +243,8 @@ describe("useInboxNotifications", () => { test("should return an error if initial call if failing", async () => { server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res(ctx.status(500)); + mockGetInboxNotifications(() => { + return HttpResponse.json(null, { status: 500 }); }) ); @@ -288,19 +286,17 @@ describe("useInboxNotifications", () => { }); server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: [], - inboxNotifications: [], - subscriptions: [], - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(() => { + return HttpResponse.json({ + threads: [], + inboxNotifications: [], + subscriptions: [], + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); @@ -327,7 +323,7 @@ describe("useInboxNotifications", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: [newInboxNotification, oldInboxNotification], @@ -344,20 +340,20 @@ describe("useInboxNotifications", () => { describe("useInboxNotifications: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should retry with exponential backoff on error", async () => { let getInboxNotificationsReqCount = 0; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { + mockGetInboxNotifications(() => { getInboxNotificationsReqCount++; - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -372,28 +368,28 @@ describe("useInboxNotifications: error", () => { }); expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => expect(getInboxNotificationsReqCount).toBe(1)); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the inbox notifications should have been made after the first retry - await waitFor(() => expect(getInboxNotificationsReqCount).toBe(2)); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(2)); expect(result.current).toEqual({ isLoading: true }); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getInboxNotificationsReqCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(3)); expect(result.current).toEqual({ isLoading: true }); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getInboxNotificationsReqCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(4)); expect(result.current).toEqual({ isLoading: true }); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getInboxNotificationsReqCount).toBe(5)); - await waitFor(() => + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(5)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, error: expect.any(Error), @@ -401,14 +397,14 @@ describe("useInboxNotifications: error", () => { ); // Wait for 5 second for the error to clear - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); expect(result.current).toEqual({ isLoading: true }); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(getInboxNotificationsReqCount).toBe(6)); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(6)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getInboxNotificationsReqCount).toBe(7)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(7)); expect(result.current).toEqual({ isLoading: true }); // and so on... @@ -418,9 +414,9 @@ describe("useInboxNotifications: error", () => { test("should not retry if a 403 Forbidden response is received from server", async () => { server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { + mockGetInboxNotifications(() => { // Return a 403 status from the server for the initial fetch - return res(ctx.status(403)); + return HttpResponse.json(null, { status: 403 }); }) ); @@ -436,7 +432,7 @@ describe("useInboxNotifications: error", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, error: expect.any(HttpError), @@ -459,34 +455,30 @@ describe("useInboxNotifications - Suspense", () => { ]; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(() => { + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }), - mockGetInboxNotificationsDelta(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: [], - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + mockGetInboxNotificationsDelta(() => { + return HttpResponse.json({ + threads: [], + inboxNotifications: [], + subscriptions, + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -509,7 +501,7 @@ describe("useInboxNotifications - Suspense", () => { expect(result.current).toEqual(null); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications, @@ -532,11 +524,11 @@ describe("useInboxNotifications - Suspense", () => { describe("useInboxNotifications: polling", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should poll inbox notifications every x seconds", async () => { const roomId = nanoid(); @@ -552,36 +544,32 @@ describe("useInboxNotifications: polling", () => { let pollerCount = 0; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { + mockGetInboxNotifications(() => { initialCount++; - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }), - mockGetInboxNotificationsDelta(async (_req, res, ctx) => { + mockGetInboxNotificationsDelta(() => { pollerCount++; - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -608,19 +596,19 @@ describe("useInboxNotifications: polling", () => { const { unmount } = render(); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(initialCount).toBe(1)); - await waitFor(() => expect(pollerCount).toBe(0)); + await vi.waitFor(() => expect(initialCount).toBe(1)); + await vi.waitFor(() => expect(pollerCount).toBe(0)); // Wait for the first polling to occur after the initial render - jest.advanceTimersByTime(60_000); + vi.advanceTimersByTime(60_000); expect(initialCount).toBe(1); - await waitFor(() => expect(pollerCount).toBe(1)); + await vi.waitFor(() => expect(pollerCount).toBe(1)); // Advance time to simulate the polling interval - jest.advanceTimersByTime(60_000); + vi.advanceTimersByTime(60_000); // Wait for the second polling to occur expect(initialCount).toBe(1); - await waitFor(() => expect(pollerCount).toBe(2)); + await vi.waitFor(() => expect(pollerCount).toBe(2)); unmount(); }); @@ -647,54 +635,49 @@ describe("useInboxNotifications: polling", () => { ]; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { - const query = _req.url.searchParams.get("query"); + mockGetInboxNotifications(async ({ request }) => { + const url = new URL(request.url); + const query = url.searchParams.get("query"); // For the sake of simplicity, the server mock assumes that if a query is provided, it's for roomA. if (query) { - return res( - ctx.json({ - threads: threads.filter((thread) => thread.roomId === roomA), - inboxNotifications: inboxNotifications.filter( - (inboxNotification) => inboxNotification.roomId === roomA - ), - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); - } - - return res( - ctx.json({ - threads, - inboxNotifications, + return HttpResponse.json({ + threads: threads.filter((thread) => thread.roomId === roomA), + inboxNotifications: inboxNotifications.filter( + (inboxNotification) => inboxNotification.roomId === roomA + ), subscriptions, groups: [], meta: { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ); + }); + } + + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }), - mockGetInboxNotificationsDelta(async (_req, res, ctx) => { - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + mockGetInboxNotificationsDelta(async () => { + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -713,7 +696,7 @@ describe("useInboxNotifications: polling", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: inboxNotifications.filter( @@ -739,7 +722,7 @@ describe("useInboxNotifications: polling", () => { expect(result2.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result2.current).toEqual({ isLoading: false, inboxNotifications, @@ -767,36 +750,32 @@ describe("useInboxNotifications: polling", () => { let pollerCount = 0; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { + mockGetInboxNotifications(() => { hasCalledGetNotifications = true; - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }), - mockGetInboxNotificationsDelta(async (_req, res, ctx) => { + mockGetInboxNotificationsDelta(() => { pollerCount++; - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -823,27 +802,27 @@ describe("useInboxNotifications: polling", () => { const { unmount: unmountComp1 } = render(); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(hasCalledGetNotifications).toBe(true)); + await vi.waitFor(() => expect(hasCalledGetNotifications).toBe(true)); expect(pollerCount).toBe(0); // Wait for the first polling to occur after the initial render - await jest.advanceTimersByTimeAsync(60_000); - await waitFor(() => expect(pollerCount).toBe(1)); + await vi.advanceTimersByTimeAsync(60_000); + await vi.waitFor(() => expect(pollerCount).toBe(1)); // Unmount Component 1 and verify that no new poll happens after the next interval unmountComp1(); // Advance time by a lot to ensure no next poll happens - await jest.advanceTimersByTimeAsync(999_999); // Wait a loooooooooooooooong time + await vi.advanceTimersByTimeAsync(999_999); // Wait a loooooooooooooooong time expect(pollerCount).toBe(1); // Mount Component 2 and verify that a new poll happens immediately (because the last time we polled was 999999ms ago) const { unmount: unmountComp2 } = render(); - await waitFor(() => expect(pollerCount).toBe(2)); + await vi.waitFor(() => expect(pollerCount).toBe(2)); // And polling keeps happening every 60s too - await jest.advanceTimersByTimeAsync(60_000); - await waitFor(() => expect(pollerCount).toBe(3)); + await vi.advanceTimersByTimeAsync(60_000); + await vi.waitFor(() => expect(pollerCount).toBe(3)); unmountComp2(); }); @@ -862,36 +841,32 @@ describe("useInboxNotifications: polling", () => { let pollerCount = 0; server.use( - mockGetInboxNotifications(async (_req, res, ctx) => { + mockGetInboxNotifications(() => { hasCalledGetNotifications = true; - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }), - mockGetInboxNotificationsDelta(async (_req, res, ctx) => { + mockGetInboxNotificationsDelta(() => { pollerCount++; - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - }, - }) - ); + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -918,21 +893,21 @@ describe("useInboxNotifications: polling", () => { const { unmount } = render(); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(hasCalledGetNotifications).toBe(true)); + await vi.waitFor(() => expect(hasCalledGetNotifications).toBe(true)); expect(pollerCount).toBe(0); // Wait for the first polling to occur after the initial render - await jest.advanceTimersByTimeAsync(60_000); - await waitFor(() => expect(pollerCount).toBe(1)); + await vi.advanceTimersByTimeAsync(60_000); + await vi.waitFor(() => expect(pollerCount).toBe(1)); // Advance 10 seconds (more than the currently set maximum stale time, 5000) - await jest.advanceTimersByTimeAsync(10_000); + await vi.advanceTimersByTimeAsync(10_000); // Dispatch a `visibilitychange` event and verify that when the document becomes // visible a new poll happens since more than 5000 ms has passed since the last poll document.dispatchEvent(new Event("visibilitychange")); - await waitFor(() => expect(pollerCount).toBe(2)); + await vi.waitFor(() => expect(pollerCount).toBe(2)); unmount(); }); @@ -940,19 +915,22 @@ describe("useInboxNotifications: polling", () => { describe("useInboxNotificationsSuspense: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should trigger error boundary if initial fetch throws an error", async () => { + let getInboxNotificationsReqCount = 0; + server.use( - mockGetInboxNotifications((_req, res, ctx) => { - // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + // Mock an error response from the server for the initial fetch + mockGetInboxNotifications(() => { + getInboxNotificationsReqCount++; + return HttpResponse.json(null, { status: 500 }); }) ); @@ -965,7 +943,7 @@ describe("useInboxNotificationsSuspense: error", () => { function Fallback({ resetErrorBoundary }: FallbackProps) { return (

-

Oops, error grabbing inbox notifications.

+
There was an error while getting inbox notifications.
); @@ -975,37 +953,55 @@ describe("useInboxNotificationsSuspense: error", () => { wrapper: ({ children }) => ( - {children} + Loading}>{children} ), }); - // Hook did not return a value. Instead, an error was thrown expect(result.current).toEqual(null); - expect(screen.getByText("Loading, yo")).toBeInTheDocument(); + expect(screen.getByText("Loading")).toBeInTheDocument(); // Wait until all fetch attempts have been done - await act(() => jest.advanceTimersToNextTimerAsync()); // fetch attempt 1 - await act(() => jest.advanceTimersByTimeAsync(5_000)); // fetch attempt 2 - await act(() => jest.advanceTimersByTimeAsync(5_000)); // fetch attempt 3 - await act(() => jest.advanceTimersByTimeAsync(10_000)); // fetch attempt 4 - await act(() => jest.advanceTimersByTimeAsync(15_000)); // fetch attempt 5 + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(1)); + + // The first retry should be made after 5s + await vi.advanceTimersByTimeAsync(5_000); + // A new fetch request for the threads should have been made after the first retry + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(2)); + + // The second retry should be made after 5s + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(3)); + + // The third retry should be made after 10s + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(4)); + + // The fourth retry should be made after 15s + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getInboxNotificationsReqCount).toBe(5)); // Check if the error boundary's fallback is displayed - expect( - screen.getByText("Oops, error grabbing inbox notifications.") - ).toBeInTheDocument(); + await vi.waitFor(() => { + expect( + screen.getByText( + "There was an error while getting inbox notifications." + ) + ).toBeInTheDocument(); + }); // Wait until the error boundary auto-clears - await act(() => jest.advanceTimersByTimeAsync(5_000)); + await vi.advanceTimersByTimeAsync(5_000); // Simulate clicking the retry button fireEvent.click(screen.getByText("Retry")); // The error boundary's fallback should be cleared - expect(screen.getByText("Loading, yo")).toBeInTheDocument(); + await vi.waitFor(() => { + expect(screen.getByText("Loading")).toBeInTheDocument(); + }); unmount(); }); @@ -1022,26 +1018,24 @@ describe("useInboxNotificationsSuspense: error", () => { let n = 0; server.use( - mockGetInboxNotifications((_req, res, ctx) => { + mockGetInboxNotifications(() => { n++; if (n <= 1) { // Mock an error response from the server - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); } // Mock a successful response from the server for the subsequent fetches - return res( - ctx.json({ - threads, - inboxNotifications, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + return HttpResponse.json({ + threads, + inboxNotifications, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); @@ -1077,7 +1071,7 @@ describe("useInboxNotificationsSuspense: error", () => { expect(screen.getByText("Loading your notifications")).toBeInTheDocument(); // Wait until all fetch attempts have been done - await act(() => jest.advanceTimersToNextTimerAsync()); // fetch attempt 1 + await act(() => vi.advanceTimersToNextTimerAsync()); // Check if the error boundary's fallback is displayed expect(screen.getByText("Done loading!")).toBeInTheDocument(); @@ -1131,73 +1125,79 @@ describe("useInboxNotifications: pagination", () => { let isPage3Requested = false; server.use( - mockGetInboxNotifications(async (req, res, ctx) => { - const url = new URL(req.url); + mockGetInboxNotifications(({ request }) => { + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); // Request for Page 2 if (cursor === "cursor-1") { isPage2Requested = true; - return res( - ctx.json({ - threads: [thread2], - inboxNotifications: inboxNotificationsPage2, - subscriptions: subscriptionsPage2, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-2", - }, - }) - ); + return HttpResponse.json({ + threads: [thread2], + inboxNotifications: inboxNotificationsPage2, + subscriptions: subscriptionsPage2, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-2", + }, + }); } // Request for Page 3 else if (cursor === "cursor-2") { isPage3Requested = true; - return res( - ctx.json({ - threads: [thread3], - inboxNotifications: inboxNotificationsPage3, - subscriptions: subscriptionsPage3, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-3", - }, - }) - ); + return HttpResponse.json({ + threads: [thread3], + inboxNotifications: inboxNotificationsPage3, + subscriptions: subscriptionsPage3, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-3", + }, + }); } // Request for the first page else { isPage1Requested = true; - return res( - ctx.json({ - threads: [thread1], - inboxNotifications: inboxNotificationsPage1, - subscriptions: subscriptionsPage1, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - }, - }) - ); - } - }), - mockGetInboxNotificationsDelta(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: [], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], + return HttpResponse.json({ + threads: [thread1], + inboxNotifications: inboxNotificationsPage1, + subscriptions: subscriptionsPage1, + groups: [], meta: { requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", }, - }) - ); + }); + } + }), + mockGetInboxNotificationsDelta(async () => { + return HttpResponse.json({ + threads: [], + inboxNotifications: [], + subscriptions: [], + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", + }, + }); + }), + mockGetInboxNotificationsDelta(() => { + return HttpResponse.json({ + threads: [], + inboxNotifications: [], + subscriptions: [], + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -1218,8 +1218,8 @@ describe("useInboxNotifications: pagination", () => { expect(result.current).toEqual({ isLoading: true }); // Initial load (Page 1) - await waitFor(() => expect(isPage1Requested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPage1Requested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: [...inboxNotificationsPage1], @@ -1234,8 +1234,8 @@ describe("useInboxNotifications: pagination", () => { // Fetch Page 2 fetchMore(); - await waitFor(() => expect(isPage2Requested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPage2Requested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: [ @@ -1251,8 +1251,8 @@ describe("useInboxNotifications: pagination", () => { // Fetch Page 3 fetchMore(); - await waitFor(() => expect(isPage3Requested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPage3Requested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: [ @@ -1308,41 +1308,37 @@ describe("useInboxNotifications: pagination", () => { let getNotificationsReqCount = 0; server.use( - mockGetInboxNotifications(async (req, res, ctx) => { + mockGetInboxNotifications(({ request }) => { getNotificationsReqCount++; - const url = new URL(req.url); + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); // Request for Page 2 (final page) if (cursor === "cursor-1") { isPageTwoRequested = true; - return res( - ctx.json({ - threads: [threadTwo], - inboxNotifications: inboxNotificationsPageTwo, - subscriptions: subscriptionsPageTwo, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + return HttpResponse.json({ + threads: [threadTwo], + inboxNotifications: inboxNotificationsPageTwo, + subscriptions: subscriptionsPageTwo, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); } // Request for the first page else { - return res( - ctx.json({ - threads: [threadOne], - inboxNotifications: inboxNotificationsPageOne, - subscriptions: subscriptionsPageOne, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - }, - }) - ); + return HttpResponse.json({ + threads: [threadOne], + inboxNotifications: inboxNotificationsPageOne, + subscriptions: subscriptionsPageOne, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", + }, + }); } }) ); @@ -1360,7 +1356,7 @@ describe("useInboxNotifications: pagination", () => { expect(result.current).toEqual({ isLoading: true }); // Initial load (Page 1) - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: [...inboxNotificationsPageOne], @@ -1376,9 +1372,9 @@ describe("useInboxNotifications: pagination", () => { // Fetch Page 2 (final page) fetchMore(); - await waitFor(() => expect(isPageTwoRequested).toBe(true)); + await vi.waitFor(() => expect(isPageTwoRequested).toBe(true)); expect(getNotificationsReqCount).toEqual(2); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: [ @@ -1414,29 +1410,27 @@ describe("useInboxNotifications: pagination", () => { ]; server.use( - mockGetInboxNotifications(async (req, res, ctx) => { - const url = new URL(req.url); + mockGetInboxNotifications(({ request }) => { + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); // Initial load (Page 1) if (cursor === null) { - return res( - ctx.json({ - threads: [threadOne], - inboxNotifications: inboxNotificationsPageOne, - subscriptions: subscriptionsPageOne, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - }, - }) - ); + return HttpResponse.json({ + threads: [threadOne], + inboxNotifications: inboxNotificationsPageOne, + subscriptions: subscriptionsPageOne, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", + }, + }); } // Page 2 else { isPageTwoRequested = true; - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); } }) ); @@ -1454,7 +1448,7 @@ describe("useInboxNotifications: pagination", () => { expect(result.current).toEqual({ isLoading: true }); // Initial load (Page 1) - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: [...inboxNotificationsPageOne], @@ -1470,8 +1464,8 @@ describe("useInboxNotifications: pagination", () => { // Fetch Page 2 (which returns an error) fetchMore(); - await waitFor(() => expect(isPageTwoRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageTwoRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, inboxNotifications: [...inboxNotificationsPageOne], @@ -1517,66 +1511,58 @@ describe("useInboxNotifications: pagination", () => { let requestCount = 0; server.use( - mockGetInboxNotifications(async (req, res, ctx) => { + mockGetInboxNotifications(async ({ request }) => { requestCount++; - const url = new URL(req.url); + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); if (cursor === "cursor-1") { - return res( - ctx.json({ - threads: [thread2], - inboxNotifications: notificationsPage2, - subscriptions: [dummySubscriptionData({ subjectId: thread2.id })], - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-2", - }, - }) - ); + return HttpResponse.json({ + threads: [thread2], + inboxNotifications: notificationsPage2, + subscriptions: [dummySubscriptionData({ subjectId: thread2.id })], + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-2", + }, + }); } else if (cursor === "cursor-2") { - return res( - ctx.json({ - threads: [thread3], - inboxNotifications: notificationsPage3, - subscriptions: [dummySubscriptionData({ subjectId: thread3.id })], - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + return HttpResponse.json({ + threads: [thread3], + inboxNotifications: notificationsPage3, + subscriptions: [dummySubscriptionData({ subjectId: thread3.id })], + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); } else { - return res( - ctx.json({ - threads: [thread1], - inboxNotifications: notificationsPage1, - subscriptions: [dummySubscriptionData({ subjectId: thread1.id })], - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - }, - }) - ); - } - }), - mockGetInboxNotificationsDelta(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: [], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], + return HttpResponse.json({ + threads: [thread1], + inboxNotifications: notificationsPage1, + subscriptions: [dummySubscriptionData({ subjectId: thread1.id })], + groups: [], meta: { requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", }, - }) - ); + }); + } + }), + mockGetInboxNotificationsDelta(async () => { + return HttpResponse.json({ + threads: [], + inboxNotifications: [], + subscriptions: [], + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + }, + }); }) ); @@ -1629,7 +1615,7 @@ describe("useInboxNotifications: pagination", () => { // Before the fix, this would stop at 2 items (page 1 + page 2) because // .finally() clearing #pendingFetchMore ran in a later microtask than // React's re-render flush, causing the 3rd fetchMore() call to be skipped. - await waitFor(() => expect(latestResult.hasFetchedAll).toBe(true), { + await vi.waitFor(() => expect(latestResult.hasFetchedAll).toBe(true), { timeout: 5000, }); expect(latestResult.count).toBe(3); diff --git a/packages/liveblocks-react/src/__tests__/useInitial.test.tsx b/packages/liveblocks-react/src/__tests__/useInitial.test.tsx index 49df1c7036a..5671777cfad 100644 --- a/packages/liveblocks-react/src/__tests__/useInitial.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useInitial.test.tsx @@ -1,4 +1,5 @@ import { renderHook } from "@testing-library/react"; +import { describe, expect, test, vi } from "vitest"; import { useInitial, useInitialUnlessFunction } from "../lib/use-initial"; @@ -36,8 +37,8 @@ describe("useInitial", () => { }); test("freezes initial function and ignores updates", () => { - const fn1 = jest.fn((a: number, b: string) => `${a}-${b}`); - const fn2 = jest.fn((a: number, b: string) => `${b}-${a}`); + const fn1 = vi.fn((a: number, b: string) => `${a}-${b}`); + const fn2 = vi.fn((a: number, b: string) => `${b}-${a}`); const { result, rerender } = renderHook((fn) => useInitial(fn), { initialProps: fn1, @@ -81,9 +82,9 @@ describe("useInitial", () => { }); test("re-evaluates functions when roomId changes", () => { - const fn1 = jest.fn(() => "result1"); - const fn2 = jest.fn(() => "result2"); - const fn3 = jest.fn(() => "result3"); + const fn1 = vi.fn(() => "result1"); + const fn2 = vi.fn(() => "result2"); + const fn3 = vi.fn(() => "result3"); const { result, rerender } = renderHook( ({ fn, roomId }) => useInitial(fn, roomId), @@ -126,8 +127,8 @@ describe("useInitialUnlessFunction", () => { }); test("creates stable wrapper that calls latest function", () => { - const fn1 = jest.fn(() => "result1"); - const fn2 = jest.fn(() => "result2"); + const fn1 = vi.fn(() => "result1"); + const fn2 = vi.fn(() => "result2"); const { result, rerender } = renderHook( ({ fn }) => useInitialUnlessFunction(fn), @@ -155,8 +156,8 @@ describe("useInitialUnlessFunction", () => { }); test("passes arguments through stable wrapper to latest function", () => { - const fn1 = jest.fn((a: number, b: string) => `${a}-${b}`); - const fn2 = jest.fn((a: number, b: string) => `${b}-${a}`); + const fn1 = vi.fn((a: number, b: string) => `${a}-${b}`); + const fn2 = vi.fn((a: number, b: string) => `${b}-${a}`); const { result, rerender } = renderHook( ({ fn }) => useInitialUnlessFunction(fn), @@ -178,9 +179,9 @@ describe("useInitialUnlessFunction", () => { }); test("maintains stable wrapper despite roomId changes", () => { - const fn1 = jest.fn(() => "fn1"); - const fn2 = jest.fn(() => "fn2"); - const fn3 = jest.fn(() => "fn3"); + const fn1 = vi.fn(() => "fn1"); + const fn2 = vi.fn(() => "fn2"); + const fn3 = vi.fn(() => "fn3"); const { result, rerender } = renderHook( ({ fn, roomId }) => useInitialUnlessFunction(fn, roomId), @@ -206,7 +207,7 @@ describe("useInitialUnlessFunction", () => { }); test("handles type changes from function to non-function when roomId changes", () => { - const fn = jest.fn(() => "function result"); + const fn = vi.fn(() => "function result"); const nonFunction = "string value"; const { result, rerender } = renderHook( @@ -232,7 +233,7 @@ describe("useInitialUnlessFunction", () => { test("handles type changes from non-function to function when roomId changes", () => { const nonFunction = "string value"; - const fn = jest.fn(() => "function result"); + const fn = vi.fn(() => "function result"); const { result, rerender } = renderHook( ({ value, roomId }) => useInitialUnlessFunction(value, roomId), diff --git a/packages/liveblocks-react/src/__tests__/useMarkAllInboxNotificationsAsRead.test.tsx b/packages/liveblocks-react/src/__tests__/useMarkAllInboxNotificationsAsRead.test.tsx index 1ed4b3cdc7d..cddb34596fb 100644 --- a/packages/liveblocks-react/src/__tests__/useMarkAllInboxNotificationsAsRead.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useMarkAllInboxNotificationsAsRead.test.tsx @@ -1,6 +1,16 @@ import { nanoid } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + test, + vi, +} from "vitest"; import { dummySubscriptionData, @@ -45,24 +55,21 @@ describe("useMarkAllInboxNotificationsAsRead", () => { ]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - inboxNotifications, - threads, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ) - ), - mockMarkAllInboxNotificationsAsRead((_req, res, ctx) => - res(ctx.status(200)) - ) + mockGetInboxNotifications(() => { + return HttpResponse.json({ + inboxNotifications, + threads, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); + }), + mockMarkAllInboxNotificationsAsRead(() => { + return HttpResponse.json(null, { status: 200 }); + }) ); const { @@ -90,7 +97,7 @@ describe("useMarkAllInboxNotificationsAsRead", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ); @@ -129,24 +136,21 @@ describe("useMarkAllInboxNotificationsAsRead", () => { ]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - inboxNotifications, - threads, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ) - ), - mockMarkAllInboxNotificationsAsRead((_req, res, ctx) => - res(ctx.status(500)) - ) + mockGetInboxNotifications(() => { + return HttpResponse.json({ + inboxNotifications, + threads, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); + }), + mockMarkAllInboxNotificationsAsRead(() => { + return HttpResponse.json(null, { status: 500 }); + }) ); const { @@ -169,7 +173,7 @@ describe("useMarkAllInboxNotificationsAsRead", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -183,7 +187,7 @@ describe("useMarkAllInboxNotificationsAsRead", () => { expect(result.current.inboxNotifications?.[0]?.readAt).not.toBe(null); expect(result.current.inboxNotifications?.[1]?.readAt).not.toBe(null); - await waitFor(() => { + await vi.waitFor(() => { // The readAt field should have been updated in the inbox notifications cache expect(result.current.inboxNotifications?.[0]?.readAt).toEqual(null); expect(result.current.inboxNotifications?.[1]?.readAt).toEqual(null); @@ -193,7 +197,7 @@ describe("useMarkAllInboxNotificationsAsRead", () => { }); test("should notify error listener when useMarkAllInboxNotificationsAsRead() fails", async () => { - const fn = jest.fn(); + const fn = vi.fn(); const roomId = nanoid(); const threads = [dummyThreadData({ roomId }), dummyThreadData({ roomId })]; @@ -217,29 +221,26 @@ describe("useMarkAllInboxNotificationsAsRead", () => { ]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - inboxNotifications, - threads, - subscriptions, - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ) - ), - mockMarkAllInboxNotificationsAsRead((_req, res, ctx) => - res( - ctx.status(500), - ctx.json({ - message: "whoops, something went wrong", - }) - ) - ) + mockGetInboxNotifications(() => { + return HttpResponse.json({ + inboxNotifications, + threads, + subscriptions, + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); + }), + mockMarkAllInboxNotificationsAsRead(() => { + return HttpResponse.json( + { message: "whoops, something went wrong" }, + { + status: 500, + } + ); + }) ); const { @@ -264,7 +265,7 @@ describe("useMarkAllInboxNotificationsAsRead", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications?.length).toBeTruthy() ); @@ -275,7 +276,7 @@ describe("useMarkAllInboxNotificationsAsRead", () => { result.current.markInboxNotificationsAsRead(); }); - await waitFor(() => { + await vi.waitFor(() => { expect(fn).toHaveBeenCalled(); }); diff --git a/packages/liveblocks-react/src/__tests__/useMarkInboxNotificationAsRead.test.tsx b/packages/liveblocks-react/src/__tests__/useMarkInboxNotificationAsRead.test.tsx index 8b1fbbeecbc..b676dbab3b2 100644 --- a/packages/liveblocks-react/src/__tests__/useMarkInboxNotificationAsRead.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useMarkInboxNotificationAsRead.test.tsx @@ -1,6 +1,16 @@ import { nanoid } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + test, + vi, +} from "vitest"; import { dummySubscriptionData, @@ -39,10 +49,9 @@ describe("useMarkInboxNotificationAsRead", () => { ]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -51,10 +60,13 @@ describe("useMarkInboxNotificationAsRead", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), - mockMarkInboxNotificationsAsRead((_req, res, ctx) => res(ctx.status(200))) + }, + { status: 200 } + ); + }), + mockMarkInboxNotificationsAsRead(() => { + return HttpResponse.json(null, { status: 200 }); + }) ); const { @@ -77,7 +89,7 @@ describe("useMarkInboxNotificationAsRead", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -108,10 +120,9 @@ describe("useMarkInboxNotificationAsRead", () => { ]; server.use( - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -120,10 +131,13 @@ describe("useMarkInboxNotificationAsRead", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), - mockMarkInboxNotificationsAsRead((_req, res, ctx) => res(ctx.status(500))) + }, + { status: 200 } + ); + }), + mockMarkInboxNotificationsAsRead(() => { + return HttpResponse.json(null, { status: 500 }); + }) ); const { @@ -146,7 +160,7 @@ describe("useMarkInboxNotificationAsRead", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -160,7 +174,7 @@ describe("useMarkInboxNotificationAsRead", () => { // We mark the notification as read optimitiscally expect(result.current.inboxNotifications?.[0]?.readAt).not.toBe(null); - await waitFor(() => { + await vi.waitFor(() => { // The readAt field should have been updated in the inbox notification cache expect(result.current.inboxNotifications?.[0]?.readAt).toEqual(null); }); diff --git a/packages/liveblocks-react/src/__tests__/useMarkThreadAsRead.test.tsx b/packages/liveblocks-react/src/__tests__/useMarkThreadAsRead.test.tsx index 12a86adf5c0..5d679b344ac 100644 --- a/packages/liveblocks-react/src/__tests__/useMarkThreadAsRead.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useMarkThreadAsRead.test.tsx @@ -1,6 +1,16 @@ import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + test, + vi, +} from "vitest"; import { dummySubscriptionData, @@ -40,15 +50,12 @@ describe("useMarkThreadAsRead", () => { ]; server.use( - mockGetThreads((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetThreads(() => { + return HttpResponse.json( + { data: threads, inboxNotifications, subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], meta: { requestedAt: new Date().toISOString(), nextCursor: null, @@ -56,13 +63,13 @@ describe("useMarkThreadAsRead", () => { [roomId]: [Permission.Write], }, }, - }) - ) - ), - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + }, + { status: 200 } + ); + }), + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -71,10 +78,13 @@ describe("useMarkThreadAsRead", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), - mockMarkInboxNotificationsAsRead((_req, res, ctx) => res(ctx.status(200))) + }, + { status: 200 } + ); + }), + mockMarkInboxNotificationsAsRead(() => { + return HttpResponse.json(null, { status: 200 }); + }) ); const { @@ -96,7 +106,7 @@ describe("useMarkThreadAsRead", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -127,16 +137,12 @@ describe("useMarkThreadAsRead", () => { ]; server.use( - mockGetThreads((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + mockGetThreads(() => { + return HttpResponse.json( + { data: threads, inboxNotifications, subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], meta: { requestedAt: new Date().toISOString(), nextCursor: null, @@ -144,13 +150,13 @@ describe("useMarkThreadAsRead", () => { [roomId]: [Permission.Write], }, }, - }) - ) - ), - mockGetInboxNotifications((_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + }, + { status: 200 } + ); + }), + mockGetInboxNotifications(() => { + return HttpResponse.json( + { inboxNotifications, threads, subscriptions, @@ -159,10 +165,13 @@ describe("useMarkThreadAsRead", () => { requestedAt: new Date().toISOString(), nextCursor: null, }, - }) - ) - ), - mockMarkInboxNotificationsAsRead((_req, res, ctx) => res(ctx.status(500))) + }, + { status: 200 } + ); + }), + mockMarkInboxNotificationsAsRead(() => { + return HttpResponse.json(null, { status: 500 }); + }) ); const { @@ -184,7 +193,7 @@ describe("useMarkThreadAsRead", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.inboxNotifications).toEqual( expect.arrayContaining(inboxNotifications) ) @@ -198,7 +207,7 @@ describe("useMarkThreadAsRead", () => { // We mark the notification as read optimitiscally expect(result.current.inboxNotifications?.[0]?.readAt).not.toBe(null); - await waitFor(() => { + await vi.waitFor(() => { // The readAt field should have been updated in the inbox notification cache expect(result.current.inboxNotifications?.[0]?.readAt).toEqual(null); }); diff --git a/packages/liveblocks-react/src/__tests__/useMarkThreadAsResolved.test.tsx b/packages/liveblocks-react/src/__tests__/useMarkThreadAsResolved.test.tsx index 9df26b34c29..2de8e03ae97 100644 --- a/packages/liveblocks-react/src/__tests__/useMarkThreadAsResolved.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useMarkThreadAsResolved.test.tsx @@ -1,6 +1,17 @@ import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyThreadData } from "./_dummies"; import MockWebSocket from "./_MockWebSocket"; @@ -29,33 +40,25 @@ describe("useMarkThreadAsResolved", () => { let hasCalledMarkThreadAsResolved = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockMarkThreadAsResolved( - { threadId: initialThread.id }, - async (_, res, ctx) => { - hasCalledMarkThreadAsResolved = true; - - return res(ctx.status(200)); - } - ) + mockMarkThreadAsResolved({ threadId: initialThread.id }, () => { + hasCalledMarkThreadAsResolved = true; + + return HttpResponse.json(null, { status: 200 }); + }) ); const { @@ -76,7 +79,7 @@ describe("useMarkThreadAsResolved", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -84,7 +87,7 @@ describe("useMarkThreadAsResolved", () => { expect(result.current.threads![0]?.resolved).toBe(true); - await waitFor(() => expect(hasCalledMarkThreadAsResolved).toEqual(true)); + await vi.waitFor(() => expect(hasCalledMarkThreadAsResolved).toEqual(true)); expect(result.current.threads![0]?.resolved).toBe(true); diff --git a/packages/liveblocks-react/src/__tests__/useMarkThreadAsUnresolved.test.tsx b/packages/liveblocks-react/src/__tests__/useMarkThreadAsUnresolved.test.tsx index 3041d33f52a..506add9a581 100644 --- a/packages/liveblocks-react/src/__tests__/useMarkThreadAsUnresolved.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useMarkThreadAsUnresolved.test.tsx @@ -1,6 +1,17 @@ import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummyThreadData } from "./_dummies"; import MockWebSocket from "./_MockWebSocket"; @@ -29,33 +40,25 @@ describe("useMarkThreadAsUnresolved", () => { let hasCalledMarkThreadAsUnresolved = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockMarkThreadAsUnresolved( - { threadId: initialThread.id }, - async (_, res, ctx) => { - hasCalledMarkThreadAsUnresolved = true; - - return res(ctx.status(200)); - } - ) + mockMarkThreadAsUnresolved({ threadId: initialThread.id }, () => { + hasCalledMarkThreadAsUnresolved = true; + + return HttpResponse.json(null, { status: 200 }); + }) ); const { @@ -76,7 +79,7 @@ describe("useMarkThreadAsUnresolved", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -84,7 +87,9 @@ describe("useMarkThreadAsUnresolved", () => { expect(result.current.threads![0]?.resolved).toBe(false); - await waitFor(() => expect(hasCalledMarkThreadAsUnresolved).toEqual(true)); + await vi.waitFor(() => + expect(hasCalledMarkThreadAsUnresolved).toEqual(true) + ); expect(result.current.threads![0]?.resolved).toBe(false); diff --git a/packages/liveblocks-react/src/__tests__/useMentionSuggestions.test.tsx b/packages/liveblocks-react/src/__tests__/useMentionSuggestions.test.tsx index 072fd5e330e..34c659e6e40 100644 --- a/packages/liveblocks-react/src/__tests__/useMentionSuggestions.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useMentionSuggestions.test.tsx @@ -1,18 +1,17 @@ import type { ResolveMentionSuggestionsArgs } from "@liveblocks/core"; import { nanoid } from "@liveblocks/core"; -import { renderHook, waitFor } from "@testing-library/react"; +import { renderHook } from "@testing-library/react"; +import { afterAll, beforeAll, describe, expect, test, vi } from "vitest"; import { useMentionSuggestions } from "../use-mention-suggestions"; import { act, createContextsForTest } from "./_utils"; -// eslint-disable-next-line @typescript-eslint/require-await async function defaultResolveMentionSuggestions({ text, }: ResolveMentionSuggestionsArgs) { return text.split("").map((id) => ({ kind: "user" as const, id })); } -// eslint-disable-next-line @typescript-eslint/require-await async function legacyResolveMentionSuggestions({ text, }: ResolveMentionSuggestionsArgs) { @@ -21,11 +20,11 @@ async function legacyResolveMentionSuggestions({ describe("useMentionSuggestions", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should return the results from resolveMentionSuggestions", async () => { @@ -50,7 +49,7 @@ describe("useMentionSuggestions", () => { expect(result.current.mentionSuggestions).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).not.toBeUndefined() ); @@ -84,7 +83,7 @@ describe("useMentionSuggestions", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "a" }, { kind: "user", id: "b" }, @@ -100,7 +99,7 @@ describe("useMentionSuggestions", () => { rerender({ text: "123" }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "1" }, { kind: "user", id: "2" }, @@ -146,7 +145,7 @@ describe("useMentionSuggestions", () => { expect(result.current.mentionSuggestions).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).not.toBeUndefined() ); @@ -165,7 +164,7 @@ describe("useMentionSuggestions", () => { test("should invoke resolveMentionSuggestions with the expected arguments", async () => { const roomId = nanoid(); - const resolveMentionSuggestions = jest.fn( + const resolveMentionSuggestions = vi.fn( ({ text }: ResolveMentionSuggestionsArgs) => text.split("").map((id) => ({ kind: "user" as const, id })) ); @@ -187,7 +186,7 @@ describe("useMentionSuggestions", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "a" }, { kind: "user", id: "b" }, @@ -206,7 +205,7 @@ describe("useMentionSuggestions", () => { test("should cache results and not invoke resolveMentionSuggestions with previously provided arguments", async () => { const roomId = nanoid(); - const resolveMentionSuggestions = jest.fn( + const resolveMentionSuggestions = vi.fn( ({ text }: ResolveMentionSuggestionsArgs) => text.split("").map((id) => ({ kind: "user" as const, id })) ); @@ -228,7 +227,7 @@ describe("useMentionSuggestions", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "a" }, { kind: "user", id: "b" }, @@ -238,7 +237,7 @@ describe("useMentionSuggestions", () => { rerender({ text: "123" }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "1" }, { kind: "user", id: "2" }, @@ -249,7 +248,7 @@ describe("useMentionSuggestions", () => { // "abc" was already resolved so resolveMentionSuggestions should not be called again rerender({ text: "abc" }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "a" }, { kind: "user", id: "b" }, @@ -275,7 +274,7 @@ describe("useMentionSuggestions", () => { test("should invoke resolveMentionSuggestions again if its cache was invalidated", async () => { const roomId = nanoid(); - const resolveMentionSuggestions = jest.fn( + const resolveMentionSuggestions = vi.fn( ({ text }: ResolveMentionSuggestionsArgs) => text.split("").map((id) => ({ kind: "user" as const, id })) ); @@ -298,7 +297,7 @@ describe("useMentionSuggestions", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "a" }, { kind: "user", id: "b" }, @@ -308,7 +307,7 @@ describe("useMentionSuggestions", () => { rerender({ text: "123" }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "1" }, { kind: "user", id: "2" }, @@ -321,7 +320,7 @@ describe("useMentionSuggestions", () => { rerender({ text: "abc" }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "a" }, { kind: "user", id: "b" }, @@ -352,7 +351,7 @@ describe("useMentionSuggestions", () => { test("should debounce the invokations of resolveMentionSuggestions", async () => { const roomId = nanoid(); - const resolveMentionSuggestions = jest.fn( + const resolveMentionSuggestions = vi.fn( ({ text }: ResolveMentionSuggestionsArgs) => text.split("").map((id) => ({ kind: "user" as const, id })) ); @@ -374,7 +373,7 @@ describe("useMentionSuggestions", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).not.toBeUndefined() ); @@ -382,7 +381,7 @@ describe("useMentionSuggestions", () => { rerender({ text: "ab" }); rerender({ text: "abc" }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).toEqual([ { kind: "user", id: "a" }, { kind: "user", id: "b" }, @@ -427,7 +426,7 @@ describe("useMentionSuggestions", () => { expect(result.current.mentionSuggestions).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.mentionSuggestions).not.toBeUndefined() ); diff --git a/packages/liveblocks-react/src/__tests__/useNotificationSettings.test.tsx b/packages/liveblocks-react/src/__tests__/useNotificationSettings.test.tsx index 9df5cb93e9f..3ec0809db65 100644 --- a/packages/liveblocks-react/src/__tests__/useNotificationSettings.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useNotificationSettings.test.tsx @@ -1,15 +1,18 @@ -import "@testing-library/jest-dom"; - -import { - act, - fireEvent, - renderHook, - screen, - waitFor, -} from "@testing-library/react"; +import { act, fireEvent, renderHook, screen } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import MockWebSocket from "./_MockWebSocket"; import { @@ -36,27 +39,25 @@ afterAll(() => server.close()); describe("useNotificationSettings", () => { test("should fetch notification settings and be referentially stable", async () => { server.use( - mockGetNotificationSettings(async (_req, res, ctx) => { - return res( - ctx.json({ - email: { - thread: true, - textMention: false, - }, - slack: { - thread: true, - textMention: false, - }, - teams: { - thread: true, - textMention: false, - }, - webPush: { - thread: true, - textMention: false, - }, - }) - ); + mockGetNotificationSettings(() => { + return HttpResponse.json({ + email: { + thread: true, + textMention: false, + }, + slack: { + thread: true, + textMention: false, + }, + teams: { + thread: true, + textMention: false, + }, + webPush: { + thread: true, + textMention: false, + }, + }); }) ); @@ -75,7 +76,7 @@ describe("useNotificationSettings", () => { expect(result.current[0]).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -109,37 +110,33 @@ describe("useNotificationSettings", () => { test("should update notification settings partially", async () => { server.use( - mockGetNotificationSettings(async (_req, res, ctx) => { - return res( - ctx.json({ - email: { - thread: true, - textMention: false, - }, - slack: { - thread: true, - textMention: false, - }, - teams: { - thread: true, - textMention: false, - }, - webPush: { - thread: true, - textMention: false, - }, - }) - ); + mockGetNotificationSettings(() => { + return HttpResponse.json({ + email: { + thread: true, + textMention: false, + }, + slack: { + thread: true, + textMention: false, + }, + teams: { + thread: true, + textMention: false, + }, + webPush: { + thread: true, + textMention: false, + }, + }); }), - mockUpdateNotificationSettings((_req, res, ctx) => { - return res( - ctx.json({ - email: { - thread: false, - textMention: false, - }, - }) - ); + mockUpdateNotificationSettings(() => { + return HttpResponse.json({ + email: { + thread: false, + textMention: false, + }, + }); }) ); @@ -155,7 +152,7 @@ describe("useNotificationSettings", () => { expect(result.current[0]).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -187,7 +184,7 @@ describe("useNotificationSettings", () => { }); }); - await waitFor(() => + await vi.waitFor(() => // Notification settings response from the server should be updated accordingly expect(result.current[0]).toEqual({ isLoading: false, @@ -217,30 +214,28 @@ describe("useNotificationSettings", () => { test("should update notification settings optimistically and revert the updates if error response from server", async () => { server.use( - mockGetNotificationSettings(async (_req, res, ctx) => { - return res( - ctx.json({ - email: { - thread: true, - textMention: false, - }, - slack: { - thread: true, - textMention: false, - }, - teams: { - thread: true, - textMention: false, - }, - webPush: { - thread: true, - textMention: false, - }, - }) - ); + mockGetNotificationSettings(() => { + return HttpResponse.json({ + email: { + thread: true, + textMention: false, + }, + slack: { + thread: true, + textMention: false, + }, + teams: { + thread: true, + textMention: false, + }, + webPush: { + thread: true, + textMention: false, + }, + }); }), - mockUpdateNotificationSettings((_req, res, ctx) => { - return res(ctx.status(500)); + mockUpdateNotificationSettings(() => { + return HttpResponse.json(null, { status: 500 }); }) ); @@ -256,7 +251,7 @@ describe("useNotificationSettings", () => { expect(result.current[0]).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -311,7 +306,7 @@ describe("useNotificationSettings", () => { }, }); - await waitFor(() => + await vi.waitFor(() => // Notification settings should be reverted to the original value after the error response from the server expect(result.current[0]).toEqual({ isLoading: false, @@ -342,21 +337,21 @@ describe("useNotificationSettings", () => { describe("useNotificationSettings: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should include an error object in the returned value if initial fetch throws an error", async () => { let getNotificationSettingsCount = 0; server.use( - mockGetNotificationSettings(async (_req, res, ctx) => { + mockGetNotificationSettings(() => { getNotificationSettingsCount++; // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -373,26 +368,26 @@ describe("useNotificationSettings: error", () => { expect(result.current[0]).toEqual({ isLoading: true }); // Wait for the first attempt to fetch channel notification settings - await waitFor(() => expect(getNotificationSettingsCount).toBe(1)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getNotificationSettingsCount).toBe(2)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(5)); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, error: expect.any(Error), @@ -400,15 +395,15 @@ describe("useNotificationSettings: error", () => { ); // Wait for 5 second for the error to clear - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); expect(result.current[0]).toEqual({ isLoading: true }); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(getNotificationSettingsCount).toBe(6)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(6)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(7)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(7)); expect(result.current[0]).toEqual({ isLoading: true }); // and so on... @@ -421,33 +416,31 @@ describe("useNotificationSettings: error", () => { let getNotificationSettingsCount = 0; server.use( - mockGetNotificationSettings(async (_req, res, ctx) => { + mockGetNotificationSettings(() => { getNotificationSettingsCount++; if (shouldReturnErrorResponse) { // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); } - return res( - ctx.json({ - email: { - thread: true, - textMention: false, - }, - slack: { - thread: true, - textMention: false, - }, - teams: { - thread: true, - textMention: false, - }, - webPush: { - thread: true, - textMention: false, - }, - }) - ); + return HttpResponse.json({ + email: { + thread: true, + textMention: false, + }, + slack: { + thread: true, + textMention: false, + }, + teams: { + thread: true, + textMention: false, + }, + webPush: { + thread: true, + textMention: false, + }, + }); }) ); @@ -464,26 +457,26 @@ describe("useNotificationSettings: error", () => { expect(result.current[0]).toEqual({ isLoading: true }); // Wait for the first attempt to fetch channel notification settings - await waitFor(() => expect(getNotificationSettingsCount).toBe(1)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getNotificationSettingsCount).toBe(2)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(5)); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, error: expect.any(Error), @@ -491,19 +484,19 @@ describe("useNotificationSettings: error", () => { ); // Advance by5 seconds and verify that error is cleared - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); expect(result.current[0]).toEqual({ isLoading: true }); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(getNotificationSettingsCount).toBe(6)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(6)); // Switch the mock endpoint to return a successful response after 4 seconds shouldReturnErrorResponse = false; // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -534,72 +527,66 @@ describe("useNotificationSettings: error", () => { test("should poll notification settings every 5 mins", async () => { let getNotificationSettingsCount = 0; server.use( - mockGetNotificationSettings(async (_req, res, ctx) => { + mockGetNotificationSettings(() => { getNotificationSettingsCount++; if (getNotificationSettingsCount === 1) { - return res( - ctx.json({ - email: { - thread: false, - textMention: true, - }, - slack: { - thread: false, - textMention: true, - }, - teams: { - thread: false, - textMention: true, - }, - webPush: { - thread: false, - textMention: true, - }, - }) - ); + return HttpResponse.json({ + email: { + thread: false, + textMention: true, + }, + slack: { + thread: false, + textMention: true, + }, + teams: { + thread: false, + textMention: true, + }, + webPush: { + thread: false, + textMention: true, + }, + }); } else if (getNotificationSettingsCount === 2) { - return res( - ctx.json({ - email: { - thread: false, - textMention: false, - }, - slack: { - thread: false, - textMention: false, - }, - teams: { - thread: false, - textMention: false, - }, - webPush: { - thread: false, - textMention: false, - }, - }) - ); - } - - return res( - ctx.json({ + return HttpResponse.json({ email: { - thread: true, + thread: false, textMention: false, }, slack: { - thread: true, + thread: false, textMention: false, }, teams: { - thread: true, + thread: false, textMention: false, }, webPush: { - thread: true, + thread: false, textMention: false, }, - }) - ); + }); + } + + return HttpResponse.json({ + email: { + thread: true, + textMention: false, + }, + slack: { + thread: true, + textMention: false, + }, + teams: { + thread: true, + textMention: false, + }, + webPush: { + thread: true, + textMention: false, + }, + }); }) ); @@ -616,7 +603,7 @@ describe("useNotificationSettings: error", () => { expect(result.current[0]).toEqual({ isLoading: true }); // Wait for the first attempt to fetch channel notification settings - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -643,8 +630,8 @@ describe("useNotificationSettings: error", () => { expect(getNotificationSettingsCount).toBe(1); // Advance by 5 minute so that and verify that the first poll is triggered - jest.advanceTimersByTime(60_000 * 5); - await waitFor(() => + vi.advanceTimersByTime(60_000 * 5); + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -670,8 +657,8 @@ describe("useNotificationSettings: error", () => { expect(getNotificationSettingsCount).toBe(2); // Advance by another 5 minute so that and verify that the second poll is triggered - jest.advanceTimersByTime(60_000 * 5); - await waitFor(() => + vi.advanceTimersByTime(60_000 * 5); + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -703,27 +690,25 @@ describe("useNotificationSettings: error", () => { describe("useNotificationSettings - Suspense", () => { test("should be referentially stable", async () => { server.use( - mockGetNotificationSettings(async (_req, res, ctx) => { - return res( - ctx.json({ - email: { - thread: true, - textMention: false, - }, - slack: { - thread: true, - textMention: false, - }, - teams: { - thread: true, - textMention: false, - }, - webPush: { - thread: true, - textMention: false, - }, - }) - ); + mockGetNotificationSettings(() => { + return HttpResponse.json({ + email: { + thread: true, + textMention: false, + }, + slack: { + thread: true, + textMention: false, + }, + teams: { + thread: true, + textMention: false, + }, + webPush: { + thread: true, + textMention: false, + }, + }); }) ); @@ -746,7 +731,7 @@ describe("useNotificationSettings - Suspense", () => { expect(result.current).toEqual(null); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -782,21 +767,21 @@ describe("useNotificationSettings - Suspense", () => { describe("useNotificationSettings - Suspense: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should trigger error boundary if initial fetch throws an error", async () => { let getNotificationSettingsCount = 0; server.use( - mockGetNotificationSettings(async (_req, res, ctx) => { + mockGetNotificationSettings(() => { getNotificationSettingsCount++; // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -831,26 +816,26 @@ describe("useNotificationSettings - Suspense: error", () => { expect(result.current).toEqual(null); // Wait for the first attempt to fetch channel notification settings - await waitFor(() => expect(getNotificationSettingsCount).toBe(1)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getNotificationSettingsCount).toBe(2)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(5)); - await waitFor(() => + await vi.waitFor(() => // Check if the error boundary's fallback is displayed expect( screen.getByText( @@ -865,10 +850,10 @@ describe("useNotificationSettings - Suspense: error", () => { test("should retry with exponential backoff on error and clear error boundary", async () => { let getNotificationSettingsCount = 0; server.use( - mockGetNotificationSettings(async (_req, res, ctx) => { + mockGetNotificationSettings(() => { getNotificationSettingsCount++; // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -903,26 +888,26 @@ describe("useNotificationSettings - Suspense: error", () => { expect(result.current).toEqual(null); // Wait for the first attempt to fetch channel notification settings - await waitFor(() => expect(getNotificationSettingsCount).toBe(1)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getNotificationSettingsCount).toBe(2)); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getNotificationSettingsCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getNotificationSettingsCount).toBe(5)); - await waitFor(() => + await vi.waitFor(() => // Check if the error boundary's fallback is displayed expect( screen.getByText( @@ -932,7 +917,7 @@ describe("useNotificationSettings - Suspense: error", () => { ); // Wait until the error boundary auto-clears - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // Simulate clicking the retry button fireEvent.click(screen.getByText("Retry")); diff --git a/packages/liveblocks-react/src/__tests__/useRoomInfo.test.tsx b/packages/liveblocks-react/src/__tests__/useRoomInfo.test.tsx index ef60343db60..aabd25ed2e0 100644 --- a/packages/liveblocks-react/src/__tests__/useRoomInfo.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useRoomInfo.test.tsx @@ -1,14 +1,12 @@ -import "@testing-library/jest-dom"; - import type { ResolveRoomsInfoArgs } from "@liveblocks/core"; import { nanoid } from "@liveblocks/core"; -import { renderHook, screen, waitFor } from "@testing-library/react"; +import { renderHook, screen } from "@testing-library/react"; import { Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { afterAll, beforeAll, describe, expect, test, vi } from "vitest"; import { act, createContextsForTest } from "./_utils"; -// eslint-disable-next-line @typescript-eslint/require-await async function defaultResolveRoomsInfo({ roomIds }: ResolveRoomsInfoArgs) { return roomIds.map((roomId) => ({ name: roomId, @@ -17,11 +15,11 @@ async function defaultResolveRoomsInfo({ roomIds }: ResolveRoomsInfoArgs) { describe("useRoomInfo", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should return an error if resolveRoomsInfo is not set", async () => { @@ -46,7 +44,9 @@ describe("useRoomInfo", () => { expect(result.current.roomInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); expect(result.current.roomInfo).toEqual({ isLoading: false, @@ -80,7 +80,9 @@ describe("useRoomInfo", () => { expect(result.current.roomInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); expect(result.current.roomInfo).toEqual({ isLoading: false, @@ -113,7 +115,9 @@ describe("useRoomInfo", () => { expect(result.current.roomInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); expect(result.current.roomInfo).toEqual({ isLoading: false, @@ -124,7 +128,9 @@ describe("useRoomInfo", () => { expect(result.current.roomInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); expect(result.current.roomInfo).toEqual({ isLoading: false, @@ -137,7 +143,7 @@ describe("useRoomInfo", () => { test("should cache results based on room ID", async () => { const roomId = nanoid(); - const resolveRoomsInfo = jest.fn(({ roomIds }: ResolveRoomsInfoArgs) => + const resolveRoomsInfo = vi.fn(({ roomIds }: ResolveRoomsInfoArgs) => roomIds.map((roomId) => ({ name: roomId })) ); const { @@ -158,11 +164,15 @@ describe("useRoomInfo", () => { } ); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); rerender({ roomId: "123" }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); rerender({ roomId: "abc" }); @@ -183,7 +193,7 @@ describe("useRoomInfo", () => { test("should revalidate instantly if its cache is invalidated", async () => { const roomId = nanoid(); - const resolveRoomsInfo = jest.fn(({ roomIds }: ResolveRoomsInfoArgs) => + const resolveRoomsInfo = vi.fn(({ roomIds }: ResolveRoomsInfoArgs) => roomIds.map((roomId) => ({ name: roomId })) ); const { @@ -205,11 +215,15 @@ describe("useRoomInfo", () => { } ); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); rerender({ roomId: "123" }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); rerender({ roomId: "abc" }); @@ -221,7 +235,9 @@ describe("useRoomInfo", () => { // Invalidate all room IDs act(() => client.resolvers.invalidateRoomsInfo()); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); expect(result.current.roomInfo).toEqual({ isLoading: false, @@ -242,7 +258,7 @@ describe("useRoomInfo", () => { test("should batch (and deduplicate) requests for the same room ID", async () => { const roomId = nanoid(); - const resolveRoomsInfo = jest.fn(({ roomIds }: ResolveRoomsInfoArgs) => + const resolveRoomsInfo = vi.fn(({ roomIds }: ResolveRoomsInfoArgs) => roomIds.map((roomId) => ({ name: roomId })) ); const { @@ -264,7 +280,7 @@ describe("useRoomInfo", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.roomInfoAbc.isLoading).toBeFalsy(); expect(result.current.roomInfoAbc2.isLoading).toBeFalsy(); expect(result.current.roomInfo123.isLoading).toBeFalsy(); @@ -315,7 +331,9 @@ describe("useRoomInfo", () => { expect(result.current.roomInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); expect(result.current.roomInfo).toEqual({ isLoading: false, @@ -348,7 +366,9 @@ describe("useRoomInfo", () => { expect(result.current.roomInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); expect(result.current.roomInfo).toEqual({ isLoading: false, @@ -381,7 +401,9 @@ describe("useRoomInfo", () => { expect(result.current.roomInfo).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); expect(result.current.roomInfo).toEqual({ isLoading: false, @@ -396,7 +418,7 @@ describe("useRoomInfo", () => { test("should return an error if resolveRoomsInfo returns undefined for a specifc room ID", async () => { const roomId = nanoid(); - const resolveRoomsInfo = jest.fn(({ roomIds }: ResolveRoomsInfoArgs) => + const resolveRoomsInfo = vi.fn(({ roomIds }: ResolveRoomsInfoArgs) => roomIds.map((roomId) => { if (roomId === "abc") { return undefined; @@ -422,7 +444,7 @@ describe("useRoomInfo", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.roomInfoAbc.isLoading).toBeFalsy(); expect(result.current.roomInfo123.isLoading).toBeFalsy(); }); @@ -447,11 +469,11 @@ describe("useRoomInfo", () => { describe("useRoomInfoSuspense", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should suspend with Suspense", async () => { @@ -482,14 +504,16 @@ describe("useRoomInfoSuspense", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is displayed expect(screen.getByText("Loading")).toBeInTheDocument(); }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is no longer displayed expect(screen.getByText("Loaded")).toBeInTheDocument(); }); @@ -526,28 +550,32 @@ describe("useRoomInfoSuspense", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is displayed expect(screen.getByText("Loading")).toBeInTheDocument(); }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is no longer displayed expect(screen.getByText("Loaded")).toBeInTheDocument(); }); act(() => client.resolvers.invalidateRoomsInfo()); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is displayed again expect(screen.getByText("Loading")).toBeInTheDocument(); }); - await waitFor(() => expect(result.current.roomInfo.isLoading).toBeFalsy()); + await vi.waitFor(() => + expect(result.current.roomInfo.isLoading).toBeFalsy() + ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is no longer displayed again expect(screen.getByText("Loaded")).toBeInTheDocument(); }); @@ -588,7 +616,7 @@ describe("useRoomInfoSuspense", () => { expect(result.current).toEqual(null); - await waitFor(() => { + await vi.waitFor(() => { // Check if the error boundary fallback is displayed expect( screen.getByText("There was an error while getting room info.") diff --git a/packages/liveblocks-react/src/__tests__/useRoomSubscriptionSettings.test.tsx b/packages/liveblocks-react/src/__tests__/useRoomSubscriptionSettings.test.tsx index fd59e8b2625..e374fe90b57 100644 --- a/packages/liveblocks-react/src/__tests__/useRoomSubscriptionSettings.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useRoomSubscriptionSettings.test.tsx @@ -1,16 +1,19 @@ -import "@testing-library/jest-dom"; - import { nanoid } from "@liveblocks/core"; -import { - act, - fireEvent, - renderHook, - screen, - waitFor, -} from "@testing-library/react"; +import { act, fireEvent, renderHook, screen } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import MockWebSocket from "./_MockWebSocket"; import { @@ -39,13 +42,11 @@ describe("useRoomSubscriptionSettings", () => { const roomId = nanoid(); server.use( - mockGetRoomSubscriptionSettings(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: "all", - textMentions: "mine", - }) - ); + mockGetRoomSubscriptionSettings(() => { + return HttpResponse.json({ + threads: "all", + textMentions: "mine", + }); }) ); @@ -64,7 +65,7 @@ describe("useRoomSubscriptionSettings", () => { expect(result.current[0]).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -87,17 +88,15 @@ describe("useRoomSubscriptionSettings", () => { const roomId = nanoid(); server.use( - mockGetRoomSubscriptionSettings(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: "all", - textMentions: "mine", - }) - ); + mockGetRoomSubscriptionSettings(() => { + return HttpResponse.json({ + threads: "all", + textMentions: "mine", + }); }), - mockUpdateRoomSubscriptionSettings((_req, res, ctx) => - res(ctx.status(500)) - ) + mockUpdateRoomSubscriptionSettings(() => { + return HttpResponse.json(null, { status: 500 }); + }) ); const { @@ -115,7 +114,7 @@ describe("useRoomSubscriptionSettings", () => { expect(result.current[0]).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -140,7 +139,7 @@ describe("useRoomSubscriptionSettings", () => { }, }); - await waitFor(() => { + await vi.waitFor(() => { // Subscription settings should be reverted to the original value ("all") after the error response from the server expect(result.current[0]).toEqual({ isLoading: false, @@ -157,12 +156,12 @@ describe("useRoomSubscriptionSettings", () => { describe("useRoomSubscriptionSettings: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should include an error object in the returned value if initial fetch throws an error", async () => { @@ -170,10 +169,10 @@ describe("useRoomSubscriptionSettings: error", () => { let getRoomSubscriptionSettingsCount = 0; server.use( - mockGetRoomSubscriptionSettings((_req, res, ctx) => { + mockGetRoomSubscriptionSettings(() => { getRoomSubscriptionSettingsCount++; // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -193,26 +192,26 @@ describe("useRoomSubscriptionSettings: error", () => { expect(result.current[0]).toEqual({ isLoading: true }); // Wait for the first attempt to fetch room subscription settings - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(1)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(2)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(5)); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, error: expect.any(Error), @@ -220,15 +219,15 @@ describe("useRoomSubscriptionSettings: error", () => { ); // Wait for 5 second for the error to clear - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); expect(result.current[0]).toEqual({ isLoading: true }); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(6)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(6)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(7)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(7)); expect(result.current[0]).toEqual({ isLoading: true }); // and so on... @@ -242,18 +241,16 @@ describe("useRoomSubscriptionSettings: error", () => { let shouldReturnErrorResponse = true; let getRoomSubscriptionSettingsCount = 0; server.use( - mockGetRoomSubscriptionSettings((_req, res, ctx) => { + mockGetRoomSubscriptionSettings(() => { getRoomSubscriptionSettingsCount++; if (shouldReturnErrorResponse) { // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); } else { - return res( - ctx.json({ - threads: "all", - textMentions: "mine", - }) - ); + return HttpResponse.json({ + threads: "all", + textMentions: "mine", + }); } }) ); @@ -274,26 +271,26 @@ describe("useRoomSubscriptionSettings: error", () => { expect(result.current[0]).toEqual({ isLoading: true }); // Wait for the first attempt to fetch room subscription settings - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(1)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(2)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(5)); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, error: expect.any(Error), @@ -301,19 +298,19 @@ describe("useRoomSubscriptionSettings: error", () => { ); // Advance by5 seconds and verify that error is cleared - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); expect(result.current[0]).toEqual({ isLoading: true }); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(6)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(6)); // Switch the mock endpoint to return a successful response after 4 seconds shouldReturnErrorResponse = false; // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -332,29 +329,23 @@ describe("useRoomSubscriptionSettings: error", () => { let getRoomSubscriptionSettingsCount = 0; server.use( - mockGetRoomSubscriptionSettings((_req, res, ctx) => { + mockGetRoomSubscriptionSettings(() => { getRoomSubscriptionSettingsCount++; if (getRoomSubscriptionSettingsCount === 1) { - return res( - ctx.json({ - threads: "all", - textMentions: "mine", - }) - ); + return HttpResponse.json({ + threads: "all", + textMentions: "mine", + }); } else if (getRoomSubscriptionSettingsCount === 2) { - return res( - ctx.json({ - threads: "none", - textMentions: "none", - }) - ); + return HttpResponse.json({ + threads: "none", + textMentions: "none", + }); } else { - return res( - ctx.json({ - threads: "replies_and_mentions", - textMentions: "mine", - }) - ); + return HttpResponse.json({ + threads: "replies_and_mentions", + textMentions: "mine", + }); } }) ); @@ -375,7 +366,7 @@ describe("useRoomSubscriptionSettings: error", () => { expect(result.current[0]).toEqual({ isLoading: true }); // Wait for the first attempt to fetch room subscription settings - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -387,8 +378,8 @@ describe("useRoomSubscriptionSettings: error", () => { expect(getRoomSubscriptionSettingsCount).toBe(1); // Advance by 1 minute so that and verify that the first poll is triggered - jest.advanceTimersByTime(60_000); - await waitFor(() => + vi.advanceTimersByTime(60_000); + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -400,8 +391,8 @@ describe("useRoomSubscriptionSettings: error", () => { expect(getRoomSubscriptionSettingsCount).toBe(2); // Advance by another 1 minute so that and verify that the second poll is triggered - jest.advanceTimersByTime(60_000); - await waitFor(() => + vi.advanceTimersByTime(60_000); + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -413,8 +404,8 @@ describe("useRoomSubscriptionSettings: error", () => { expect(getRoomSubscriptionSettingsCount).toBe(3); // Advance by another 1 minute so that and verify that the third poll is triggered - jest.advanceTimersByTime(60_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); + vi.advanceTimersByTime(60_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -432,13 +423,11 @@ describe("useRoomSubscriptionSettings suspense", () => { const roomId = nanoid(); server.use( - mockGetRoomSubscriptionSettings(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: "all", - textMentions: "mine", - }) - ); + mockGetRoomSubscriptionSettings(() => { + return HttpResponse.json({ + threads: "all", + textMentions: "mine", + }); }) ); @@ -459,7 +448,7 @@ describe("useRoomSubscriptionSettings suspense", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current[0]).toEqual({ isLoading: false, settings: { @@ -483,21 +472,21 @@ describe("useRoomSubscriptionSettingsSuspense: error", () => { const roomId = nanoid(); beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should trigger error boundary if initial fetch throws an error", async () => { let getRoomSubscriptionSettingsCount = 0; server.use( - mockGetRoomSubscriptionSettings((_req, res, ctx) => { + mockGetRoomSubscriptionSettings(() => { getRoomSubscriptionSettingsCount++; // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -535,26 +524,26 @@ describe("useRoomSubscriptionSettingsSuspense: error", () => { expect(result.current).toEqual(null); // Wait for the first attempt to fetch room subscription settings - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(1)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(2)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(5)); - await waitFor(() => + await vi.waitFor(() => // Check if the error boundary's fallback is displayed expect( screen.getByText( @@ -569,10 +558,10 @@ describe("useRoomSubscriptionSettingsSuspense: error", () => { test("should retry with exponential backoff on error and clear error boundary", async () => { let getRoomSubscriptionSettingsCount = 0; server.use( - mockGetRoomSubscriptionSettings((_req, res, ctx) => { + mockGetRoomSubscriptionSettings(() => { getRoomSubscriptionSettingsCount++; // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -610,26 +599,26 @@ describe("useRoomSubscriptionSettingsSuspense: error", () => { expect(result.current).toEqual(null); // Wait for the first attempt to fetch room subscription settings - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(1)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(2)); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getRoomSubscriptionSettingsCount).toBe(5)); - await waitFor(() => + await vi.waitFor(() => // Check if the error boundary's fallback is displayed expect( screen.getByText( @@ -639,13 +628,13 @@ describe("useRoomSubscriptionSettingsSuspense: error", () => { ); // Wait until the error boundary auto-clears - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // Simulate clicking the retry button fireEvent.click(screen.getByText("Retry")); // The error boundary's fallback should be cleared - await waitFor(() => { + await vi.waitFor(() => { expect(screen.getByText("Loading")).toBeInTheDocument(); }); diff --git a/packages/liveblocks-react/src/__tests__/useSubscribeToThread.test.tsx b/packages/liveblocks-react/src/__tests__/useSubscribeToThread.test.tsx index d1c1819d776..26904241e13 100644 --- a/packages/liveblocks-react/src/__tests__/useSubscribeToThread.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useSubscribeToThread.test.tsx @@ -1,6 +1,17 @@ import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummySubscriptionData, @@ -33,39 +44,29 @@ describe("useSubscribeToThread", () => { let hasCalledSubscribeToThread = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockSubscribeToThread( - { threadId: initialThread.id }, - async (_, res, ctx) => { - hasCalledSubscribeToThread = true; - - return res( - ctx.json({ - kind: "thread", - subjectId: initialThread.id, - createdAt: Date.now(), - }) - ); - } - ) + mockSubscribeToThread({ threadId: initialThread.id }, () => { + hasCalledSubscribeToThread = true; + + return HttpResponse.json({ + kind: "thread", + subjectId: initialThread.id, + createdAt: Date.now(), + }); + }) ); const { @@ -92,7 +93,7 @@ describe("useSubscribeToThread", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -101,7 +102,7 @@ describe("useSubscribeToThread", () => { act(() => result.current.subscribeToThread(initialThread.id)); - await waitFor(() => expect(hasCalledSubscribeToThread).toEqual(true)); + await vi.waitFor(() => expect(hasCalledSubscribeToThread).toEqual(true)); // The thread should optimistically be subscribed to expect(result.current.subscription.status).toBe("subscribed"); @@ -118,44 +119,35 @@ describe("useSubscribeToThread", () => { let hasCalledSubscribeToThread = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications, - subscriptions: [ - dummySubscriptionData({ - kind: "thread", - subjectId: initialThread.id, - createdAt: initialThread.createdAt, - }), - ], - deletedThreads: [], - deletedInboxNotifications: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, - }, - }) - ); - }), - mockSubscribeToThread( - { threadId: initialThread.id }, - async (_, res, ctx) => { - hasCalledSubscribeToThread = true; - - return res( - ctx.json({ + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications, + subscriptions: [ + dummySubscriptionData({ kind: "thread", subjectId: initialThread.id, - createdAt: Date.now(), - }) - ); - } - ) + createdAt: initialThread.createdAt, + }), + ], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], + }, + }, + }); + }), + mockSubscribeToThread({ threadId: initialThread.id }, () => { + hasCalledSubscribeToThread = true; + + return HttpResponse.json({ + kind: "thread", + subjectId: initialThread.id, + createdAt: Date.now(), + }); + }) ); const { @@ -182,7 +174,7 @@ describe("useSubscribeToThread", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -190,7 +182,7 @@ describe("useSubscribeToThread", () => { act(() => result.current.subscribeToThread(initialThread.id)); - await waitFor(() => expect(hasCalledSubscribeToThread).toEqual(true)); + await vi.waitFor(() => expect(hasCalledSubscribeToThread).toEqual(true)); expect(result.current.subscription.status).toBe("subscribed"); diff --git a/packages/liveblocks-react/src/__tests__/useThreadSubscription.test.tsx b/packages/liveblocks-react/src/__tests__/useThreadSubscription.test.tsx index f092fd8f651..29297c1664f 100644 --- a/packages/liveblocks-react/src/__tests__/useThreadSubscription.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useThreadSubscription.test.tsx @@ -1,6 +1,17 @@ import { nanoid, Permission } from "@liveblocks/core"; -import { renderHook, waitFor } from "@testing-library/react"; +import { renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummySubscriptionData, @@ -38,21 +49,19 @@ describe("useThreadSubscription", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications, - subscriptions, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications, + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -79,7 +88,7 @@ describe("useThreadSubscription", () => { unsubscribe: expect.any(Function), }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual({ isLoading: false, threads, @@ -115,21 +124,19 @@ describe("useThreadSubscription", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications, - subscriptions, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications, + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -156,7 +163,7 @@ describe("useThreadSubscription", () => { unsubscribe: expect.any(Function), }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual({ isLoading: false, threads, @@ -182,21 +189,19 @@ describe("useThreadSubscription", () => { const threads = [dummyThreadData({ roomId })]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -223,7 +228,7 @@ describe("useThreadSubscription", () => { unsubscribe: expect.any(Function), }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual({ isLoading: false, threads, @@ -254,21 +259,19 @@ describe("useThreadSubscription", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications, - subscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications, + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -295,7 +298,7 @@ describe("useThreadSubscription", () => { unsubscribe: expect.any(Function), }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual({ isLoading: false, threads, @@ -329,21 +332,19 @@ describe("useThreadSubscription", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications, - subscriptions, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications, + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -370,7 +371,7 @@ describe("useThreadSubscription", () => { unsubscribe: expect.any(Function), }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual({ isLoading: false, threads, diff --git a/packages/liveblocks-react/src/__tests__/useThreads.test.tsx b/packages/liveblocks-react/src/__tests__/useThreads.test.tsx index c9429da8051..6a7f4d72843 100644 --- a/packages/liveblocks-react/src/__tests__/useThreads.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useThreads.test.tsx @@ -1,8 +1,7 @@ -import "@testing-library/jest-dom"; - import type { InboxNotificationData, InboxNotificationDataPlain, + SubscriptionData, ThreadData, } from "@liveblocks/core"; import { HttpError, nanoid, Permission, ServerMsgCode } from "@liveblocks/core"; @@ -12,15 +11,24 @@ import { render, renderHook, screen, - waitFor, } from "@testing-library/react"; import { addSeconds } from "date-fns"; -import type { ResponseResolver, RestContext, RestRequest } from "msw"; -import { rest } from "msw"; +import type { HttpResponseResolver } from "msw"; +import { delay, http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; import type { ReactNode } from "react"; import { createContext, Suspense, useContext, useState } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummySubscriptionData, @@ -49,21 +57,23 @@ beforeEach(() => { afterEach(() => { MockWebSocket.reset(); server.resetHandlers(); - jest.clearAllTimers(); - jest.clearAllMocks(); + vi.clearAllTimers(); + vi.clearAllMocks(); }); afterAll(() => server.close()); function mockGetThreadsSince( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + never, { data: ThreadData[]; inboxNotifications: InboxNotificationData[]; + subscriptions: SubscriptionData[]; deletedThreads: ThreadData[]; deletedInboxNotifications: InboxNotificationDataPlain[]; + deletedSubscriptions: SubscriptionData[]; meta: { requestedAt: string; permissionHints: Record; @@ -71,7 +81,7 @@ function mockGetThreadsSince( } > ) { - return rest.get( + return http.get( "https://api.liveblocks.io/v2/c/rooms/:roomId/threads/delta", resolver ); @@ -79,11 +89,11 @@ function mockGetThreadsSince( describe("useThreads", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should fetch threads", async () => { @@ -94,24 +104,19 @@ describe("useThreads", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -127,7 +132,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads, @@ -149,24 +154,19 @@ describe("useThreads", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -182,7 +182,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads, @@ -211,25 +211,20 @@ describe("useThreads", () => { let getThreadsReqCount = 0; server.use( - mockGetThreads(async (_req, res, ctx) => { + mockGetThreads(() => { getThreadsReqCount++; - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -250,7 +245,7 @@ describe("useThreads", () => { } ); - await waitFor(() => expect(getThreadsReqCount).toBe(1)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(1)); rerender(); @@ -275,30 +270,27 @@ describe("useThreads", () => { }); server.use( - mockGetThreads(async (req, res, ctx) => { - const query = req.url.searchParams.get("query"); + mockGetThreads(({ request }) => { + const url = new URL(request.url); + const query = url.searchParams.get("query"); const pred = query ? makeThreadFilter(query) : () => true; const filteredThreads = [pinnedThread, unpinnedThread].filter(pred); const subscriptions = filteredThreads.map((thread) => dummySubscriptionData({ subjectId: thread.id }) ); - return res( - ctx.json({ - data: filteredThreads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: filteredThreads, + inboxNotifications: [], + subscriptions, + + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -319,7 +311,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [pinnedThread], @@ -351,8 +343,8 @@ describe("useThreads", () => { }); server.use( - mockGetThreads(async (req, res, ctx) => { - const query = req.url.searchParams.get("query"); + mockGetThreads(async ({ request }) => { + const query = new URL(request.url).searchParams.get("query"); const pred = query ? makeThreadFilter(query) : () => true; const filteredThreads = [thread1, thread2].filter(pred); @@ -360,23 +352,19 @@ describe("useThreads", () => { const subscriptions = filteredThreads.map((thread) => dummySubscriptionData({ subjectId: thread.id }) ); - return res( - ctx.json({ - data: filteredThreads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: filteredThreads, + inboxNotifications: [], + subscriptions, + + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -412,7 +400,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [thread1], @@ -457,30 +445,25 @@ describe("useThreads", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [ - bluePinnedThread, - blueUnpinnedThread, - redPinnedThread, - redUnpinnedThread, - uncoloredPinnedThread, - ], // removed any filtering so that we ensure the filtering is done properly on the client side, it shouldn't matter what the server returns - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [ + bluePinnedThread, + blueUnpinnedThread, + redPinnedThread, + redUnpinnedThread, + uncoloredPinnedThread, + ], // removed any filtering so that we ensure the filtering is done properly on the client side, it shouldn't matter what the server returns + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -507,7 +490,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [redPinnedThread], @@ -537,7 +520,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [redPinnedThread, redUnpinnedThread], @@ -576,7 +559,7 @@ describe("useThreads", () => { expect.objectContaining({ isLoading: false }) ); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [redPinnedThread], @@ -606,7 +589,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [], @@ -636,7 +619,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [], @@ -663,7 +646,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [bluePinnedThread, redPinnedThread, uncoloredPinnedThread], @@ -693,7 +676,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [bluePinnedThread], @@ -727,7 +710,7 @@ describe("useThreads", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [bluePinnedThread, redPinnedThread, uncoloredPinnedThread], @@ -760,7 +743,7 @@ describe("useThreads", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [uncoloredPinnedThread], @@ -791,7 +774,7 @@ describe("useThreads", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [uncoloredPinnedThread], @@ -818,7 +801,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [bluePinnedThread, redUnpinnedThread, uncoloredPinnedThread], @@ -845,7 +828,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [blueUnpinnedThread, redPinnedThread], @@ -878,7 +861,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [redUnpinnedThread], @@ -921,28 +904,23 @@ describe("useThreads", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [ - liveblocksEngineeringThread, - liveblocksDesignThread, - acmeEngineeringThread, - ], - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [ + liveblocksEngineeringThread, + liveblocksDesignThread, + acmeEngineeringThread, + ], + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -972,7 +950,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [liveblocksEngineeringThread, liveblocksDesignThread], @@ -995,25 +973,20 @@ describe("useThreads", () => { let getThreadsReqCount = 0; server.use( - mockGetThreads(async (_req, res, ctx) => { + mockGetThreads(() => { getThreadsReqCount++; - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -1035,7 +1008,7 @@ describe("useThreads", () => { } ); - await waitFor(() => expect(getThreadsReqCount).toBe(1)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(1)); unmount(); }); @@ -1056,30 +1029,26 @@ describe("useThreads", () => { }); server.use( - mockGetThreads(async (req, res, ctx) => { - const query = req.url.searchParams.get("query"); + mockGetThreads(({ request }) => { + const url = new URL(request.url); + const query = url.searchParams.get("query"); const pred = query ? makeThreadFilter(query) : () => true; const filteredThreads = [pinnedThread, unpinnedThread].filter(pred); const subscriptions = filteredThreads.map((thread) => dummySubscriptionData({ subjectId: thread.id }) ); - return res( - ctx.json({ - data: filteredThreads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: filteredThreads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -1102,7 +1071,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [pinnedThread], @@ -1117,7 +1086,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [unpinnedThread], @@ -1156,47 +1125,37 @@ describe("useThreads", () => { ]; server.use( - mockGetThreads((req, res, ctx) => { - const roomId = req.params.roomId; + mockGetThreads(({ params }) => { + const roomId = params.roomId; if (roomId === room1Id) { - return res( - ctx.json({ - data: room1Threads, - inboxNotifications: [], - subscriptions: room1Subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: room1Threads, + inboxNotifications: [], + subscriptions: room1Subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } else if (roomId === room2Id) { - return res( - ctx.json({ - data: room2Threads, - inboxNotifications: [], - subscriptions: room2Subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: room2Threads, + inboxNotifications: [], + subscriptions: room2Subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } - return res(ctx.status(404)); + return HttpResponse.json(null, { status: 404 }); }) ); @@ -1225,7 +1184,7 @@ describe("useThreads", () => { expect(room1Result.current).toEqual({ isLoading: true }); expect(room2Result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(room1Result.current).toEqual({ isLoading: false, threads: room1Threads, @@ -1236,7 +1195,7 @@ describe("useThreads", () => { }) ); - await waitFor(() => + await vi.waitFor(() => expect(room2Result.current).toEqual({ isLoading: false, threads: room2Threads, @@ -1264,47 +1223,37 @@ describe("useThreads", () => { ]; server.use( - mockGetThreads((req, res, ctx) => { - const roomId = req.params.roomId; + mockGetThreads(({ params }) => { + const roomId = params.roomId; if (roomId === room1Id) { - return res( - ctx.json({ - data: room1Threads, - inboxNotifications: [], - subscriptions: room1Subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: room1Threads, + inboxNotifications: [], + subscriptions: room1Subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } else if (roomId === room2Id) { - return res( - ctx.json({ - data: room2Threads, - inboxNotifications: [], - subscriptions: room2Subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: room2Threads, + inboxNotifications: [], + subscriptions: room2Subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } - return res(ctx.status(404)); + return HttpResponse.json(null, { status: 404 }); }) ); @@ -1338,7 +1287,7 @@ describe("useThreads", () => { expect(result.current.state).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.state).toEqual({ isLoading: false, threads: room1Threads, @@ -1355,7 +1304,7 @@ describe("useThreads", () => { expect(result.current.state).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.state).toEqual({ isLoading: false, threads: room2Threads, @@ -1370,7 +1319,7 @@ describe("useThreads", () => { result.current.setRoomId?.(room1Id); }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.state).toEqual({ isLoading: false, threads: room1Threads, @@ -1388,9 +1337,9 @@ describe("useThreads", () => { const roomId = nanoid(); server.use( - mockGetThreads((_req, res, ctx) => { + mockGetThreads(() => { // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -1406,20 +1355,20 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await jest.advanceTimersToNextTimerAsync(); // fetch attempt 1 + await vi.advanceTimersToNextTimerAsync(); // fetch attempt 1 - await jest.advanceTimersByTimeAsync(5_000); // fetch attempt 2 + await vi.advanceTimersByTimeAsync(5_000); // fetch attempt 2 expect(result.current).toEqual({ isLoading: true }); - await jest.advanceTimersByTimeAsync(5_000); // fetch attempt 3 + await vi.advanceTimersByTimeAsync(5_000); // fetch attempt 3 expect(result.current).toEqual({ isLoading: true }); - await jest.advanceTimersByTimeAsync(10_000); // fetch attempt 4 + await vi.advanceTimersByTimeAsync(10_000); // fetch attempt 4 expect(result.current).toEqual({ isLoading: true }); - await jest.advanceTimersByTimeAsync(15_000); // fetch attempt 5 + await vi.advanceTimersByTimeAsync(15_000); // fetch attempt 5 - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, error: expect.any(Error), @@ -1441,24 +1390,19 @@ describe("useThreads", () => { }); server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [newThread, oldThread], // The order is intentionally reversed to test if the hook sorts the threads by creation date - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [newThread, oldThread], // The order is intentionally reversed to test if the hook sorts the threads by creation date + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -1479,7 +1423,7 @@ describe("useThreads", () => { expect(result.current.threads).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual({ isLoading: false, threads: [oldThread, newThread], @@ -1510,40 +1454,33 @@ describe("useThreads", () => { const subscription = dummySubscriptionData({ subjectId: oldThread.id }); server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [newThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [newThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockGetInboxNotifications(async (_req, res, ctx) => { + mockGetInboxNotifications(async () => { // Mock a delay in response so that GET THREADS request is resolved before GET NOTIFICATIONS request - ctx.delay(100); - return res( - ctx.json({ - threads: [oldThread], - inboxNotifications: [inboxNotification], - subscriptions: [subscription], - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + await delay(100); + return HttpResponse.json({ + threads: [oldThread], + inboxNotifications: [inboxNotification], + subscriptions: [subscription], + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); @@ -1567,9 +1504,9 @@ describe("useThreads", () => { expect(result.current.threads).toEqual({ isLoading: true }); expect(result.current.inboxNotifications).toEqual({ isLoading: true }); - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual({ isLoading: false, threads: [oldThread, newThread], @@ -1600,40 +1537,33 @@ describe("useThreads", () => { const subscription = dummySubscriptionData({ subjectId: newThread.id }); server.use( - mockGetThreads(async (_req, res, ctx) => { + mockGetThreads(async () => { // Mock a delay in response so that GET THREADS request is resolved after GET NOTIFICATIONS request - ctx.delay(100); - return res( - ctx.json({ - data: [oldThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + await delay(100); + return HttpResponse.json({ + data: [oldThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockGetInboxNotifications(async (_req, res, ctx) => { - return res( - ctx.json({ - threads: [newThread], - inboxNotifications: [inboxNotification], - subscriptions: [subscription], - groups: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - }, - }) - ); + mockGetInboxNotifications(() => { + return HttpResponse.json({ + threads: [newThread], + inboxNotifications: [inboxNotification], + subscriptions: [subscription], + groups: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + }, + }); }) ); @@ -1657,9 +1587,9 @@ describe("useThreads", () => { expect(result.current.threads).toEqual({ isLoading: true }); expect(result.current.inboxNotifications).toEqual({ isLoading: true }); - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual({ isLoading: false, threads: [oldThread, newThread], @@ -1683,24 +1613,19 @@ describe("useThreads", () => { const subscriptions = [dummySubscriptionData({ subjectId: thread1.id })]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [thread1], - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [thread1], + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -1724,7 +1649,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [thread1], // thread2WithDeleteAt should not be returned @@ -1749,27 +1674,22 @@ describe("useThreads", () => { let getThreadsSinceReqCount = 0; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - deletedThreads: [], - inboxNotifications: [], - deletedInboxNotifications: [], - subscriptions, - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockGetThreadsSince(async (req, res, ctx) => { - const url = new URL(req.url); + mockGetThreadsSince(({ request }) => { + const url = new URL(request.url); const since = url.searchParams.get("since"); if (since) { @@ -1781,25 +1701,23 @@ describe("useThreads", () => { dummySubscriptionData({ subjectId: thread.id }) ); - return res( - ctx.json({ - data: updatedThreads, - deletedThreads: [], - inboxNotifications: [], - subscriptions: updatedSubscriptions, - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: updatedThreads, + deletedThreads: [], + inboxNotifications: [], + subscriptions: updatedSubscriptions, + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -1816,7 +1734,7 @@ describe("useThreads", () => { expect(firstRenderResult.result.current).toEqual({ isLoading: true }); // Threads should be displayed after the server responds with the threads - await waitFor(() => + await vi.waitFor(() => expect(firstRenderResult.result.current).toEqual({ isLoading: false, threads, @@ -1828,8 +1746,8 @@ describe("useThreads", () => { ); // Advance time to trigger the first poll and verify that a poll does occur - await jest.advanceTimersByTimeAsync(5 * 60_000); - await waitFor(() => expect(getThreadsSinceReqCount).toBe(1)); + await vi.advanceTimersByTimeAsync(5 * 60_000); + await vi.waitFor(() => expect(getThreadsSinceReqCount).toBe(1)); firstRenderResult.unmount(); @@ -1837,7 +1755,7 @@ describe("useThreads", () => { threads = [...originalThreads, dummyThreadData({ roomId })]; // Advance time by at least maximum stale time (5000ms) so that a poll happens immediately after the room is mounted. - await jest.advanceTimersByTimeAsync(6_000); + await vi.advanceTimersByTimeAsync(6_000); // Render the RoomProvider again and verify the threads are updated const secondRenderResult = renderHook(() => useThreads(), { @@ -1857,7 +1775,7 @@ describe("useThreads", () => { }); // The updated threads should be displayed after the server responds with the updated threads - await waitFor(() => { + await vi.waitFor(() => { expect(secondRenderResult.result.current).toEqual({ isLoading: false, threads, @@ -1879,25 +1797,20 @@ describe("useThreads", () => { let getThreadsReqCount = 0; server.use( - mockGetThreads(async (_req, res, ctx) => { + mockGetThreads(() => { getThreadsReqCount++; - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -1929,7 +1842,7 @@ describe("useThreads", () => { const { unmount: unmountSecondRoom } = render(); // A new fetch request for the threads should have been made - await waitFor(() => expect(getThreadsReqCount).toBe(1)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(1)); const room = client.getRoom(roomId); expect(room).not.toBeNull(); @@ -1952,27 +1865,22 @@ describe("useThreads", () => { let getThreadsSinceReqCount = 0; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - deletedThreads: [], - inboxNotifications: [], - deletedInboxNotifications: [], - subscriptions: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockGetThreadsSince(async (req, res, ctx) => { - const url = new URL(req.url); + mockGetThreadsSince(({ request }) => { + const url = new URL(request.url); const since = url.searchParams.get("since"); if (since) { @@ -1981,25 +1889,23 @@ describe("useThreads", () => { return thread.updatedAt >= new Date(since); }); - return res( - ctx.json({ - data: updatedThreads, - deletedThreads: [], - inboxNotifications: [], - deletedInboxNotifications: [], - subscriptions: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: updatedThreads, + inboxNotifications: [], + subscriptions: [], + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -2016,7 +1922,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); // Threads should be displayed after the server responds with the threads - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads, @@ -2028,20 +1934,20 @@ describe("useThreads", () => { ); // Advance time to trigger the first poll and verify that a poll does occur - await jest.advanceTimersByTimeAsync(5 * 60_000); - await waitFor(() => expect(getThreadsSinceReqCount).toBe(1)); + await vi.advanceTimersByTimeAsync(5 * 60_000); + await vi.waitFor(() => expect(getThreadsSinceReqCount).toBe(1)); // Add a new thread to the threads array to simulate a new thread being added to the room threads.push(dummyThreadData({ roomId })); // Advance time by at least maximum stale time (5000ms) so that a poll happens immediately after the room is mounted. - await jest.advanceTimersByTimeAsync(6_000); + await vi.advanceTimersByTimeAsync(6_000); // Simulate browser going online window.dispatchEvent(new Event("online")); // The updated threads should be displayed after the server responds with the updated threads (either due to a fetch request to get all threads or just the updated threads) - await waitFor(() => { + await vi.waitFor(() => { expect(result.current).toEqual({ isLoading: false, threads, @@ -2064,31 +1970,29 @@ describe("useThreads", () => { const threads = [dummyThreadData({ roomId })]; server.use( - mockGetThreads(async (_req, res, ctx) => { + mockGetThreads(() => { // Return a 404 to simulate the room not found getThreadsReqCount++; - return res(ctx.status(404)); + return HttpResponse.json(null, { status: 404 }); }), - mockGetThreadsSince(async (_req, res, ctx) => { + mockGetThreadsSince(() => { // Let's say the room was created after the initial fetch but before the poll, // so, new threads are available in the room getThreadsSinceReqCount++; - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions: [], + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -2104,7 +2008,7 @@ describe("useThreads", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [], @@ -2119,8 +2023,8 @@ describe("useThreads", () => { expect(getThreadsSinceReqCount).toBe(0); // Wait for the first polling to occur after the initial render - jest.advanceTimersByTime(5 * MINUTES); - await waitFor(() => + vi.advanceTimersByTime(5 * MINUTES); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads, @@ -2149,27 +2053,22 @@ describe("useThreads", () => { let getThreadsSinceReqCount = 0; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - deletedThreads: [], - inboxNotifications: [], - deletedInboxNotifications: [], - subscriptions, - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(async () => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockGetThreadsSince(async (req, res, ctx) => { - const url = new URL(req.url); + mockGetThreadsSince(async ({ request }) => { + const url = new URL(request.url); const since = url.searchParams.get("since"); getThreadsSinceReqCount++; @@ -2180,25 +2079,23 @@ describe("useThreads", () => { dummySubscriptionData({ subjectId: thread2.id }), ]; - return res( - ctx.json({ - data: [], - deletedThreads: [], - inboxNotifications: [], - subscriptions: updatedSubscriptions, - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: [], + inboxNotifications: [], + subscriptions: updatedSubscriptions, + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -2223,7 +2120,7 @@ describe("useThreads", () => { expect(firstRenderResult.result.current).toEqual({ isLoading: true }); // Threads should be displayed after the server responds with the threads - await waitFor(() => + await vi.waitFor(() => expect(firstRenderResult.result.current).toEqual({ isLoading: false, threads: [thread1], @@ -2235,13 +2132,13 @@ describe("useThreads", () => { ); // Advance time to trigger the first poll and verify that a poll does occur - await jest.advanceTimersByTimeAsync(5 * 60_000); - await waitFor(() => expect(getThreadsSinceReqCount).toBe(1)); + await vi.advanceTimersByTimeAsync(5 * 60_000); + await vi.waitFor(() => expect(getThreadsSinceReqCount).toBe(1)); firstRenderResult.unmount(); // Advance time by at least maximum stale time (5000ms) so that a poll happens immediately after the room is mounted. - await jest.advanceTimersByTimeAsync(6_000); + await vi.advanceTimersByTimeAsync(6_000); // Render the RoomProvider again and verify the threads are updated const secondRenderResult = renderHook( @@ -2269,7 +2166,7 @@ describe("useThreads", () => { }); // The updated threads should be displayed after the server responds with the updated threads - await waitFor(() => { + await vi.waitFor(() => { expect(secondRenderResult.result.current).toEqual({ isLoading: false, threads: [thread1, thread2], @@ -2285,12 +2182,12 @@ describe("useThreads", () => { describe("useThreads: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should retry with exponential backoff on error", async () => { @@ -2298,10 +2195,10 @@ describe("useThreads: error", () => { let getThreadsReqCount = 0; server.use( - mockGetThreads((_req, res, ctx) => { + mockGetThreads(() => { getThreadsReqCount++; // Mock an error response from the server for the initial fetch - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -2318,28 +2215,28 @@ describe("useThreads: error", () => { expect(result.current).toEqual({ isLoading: true }); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(getThreadsReqCount).toBe(1)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getThreadsReqCount).toBe(2)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(2)); expect(result.current).toEqual({ isLoading: true }); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getThreadsReqCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(3)); expect(result.current).toEqual({ isLoading: true }); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getThreadsReqCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(4)); expect(result.current).toEqual({ isLoading: true }); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getThreadsReqCount).toBe(5)); - await waitFor(() => { + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(5)); + await vi.waitFor(() => { expect(result.current).toEqual({ isLoading: false, error: expect.any(Error), @@ -2347,14 +2244,14 @@ describe("useThreads: error", () => { }); // Wait for 5 second for the error to clear - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); expect(result.current).toEqual({ isLoading: true }); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(getThreadsReqCount).toBe(6)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(6)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getThreadsReqCount).toBe(7)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(7)); expect(result.current).toEqual({ isLoading: true }); // and so on... @@ -2366,9 +2263,9 @@ describe("useThreads: error", () => { const roomId = nanoid(); server.use( - mockGetThreads((_req, res, ctx) => { + mockGetThreads(() => { // Return a 403 status from the server for the initial fetch - return res(ctx.status(403)); + return HttpResponse.json(null, { status: 403 }); }) ); @@ -2384,7 +2281,7 @@ describe("useThreads: error", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current).toEqual({ isLoading: false, error: expect.any(HttpError), @@ -2397,11 +2294,11 @@ describe("useThreads: error", () => { describe("useThreads: polling", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should poll threads every x seconds", async () => { const roomId = nanoid(); @@ -2413,44 +2310,37 @@ describe("useThreads: polling", () => { let getThreadsReqCount = 0; server.use( - mockGetThreads(async (_req, res, ctx) => { + mockGetThreads(() => { getThreadsReqCount++; - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: now, - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: now, + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockGetThreadsSince(async (_req, res, ctx) => { + mockGetThreadsSince(() => { getThreadsReqCount++; - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: now, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: now, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -2474,16 +2364,16 @@ describe("useThreads: polling", () => { const { unmount } = render(); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(getThreadsReqCount).toBe(1)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(1)); // Wait for the first polling to occur after the initial render - jest.advanceTimersByTime(5 * MINUTES); - await waitFor(() => expect(getThreadsReqCount).toBe(2)); + vi.advanceTimersByTime(5 * MINUTES); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(2)); // Advance time to simulate the polling interval - jest.advanceTimersByTime(5 * MINUTES); + vi.advanceTimersByTime(5 * MINUTES); // Wait for the second polling to occur - await waitFor(() => expect(getThreadsReqCount).toBe(3)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(3)); unmount(); }); @@ -2498,25 +2388,20 @@ describe("useThreads: polling", () => { let hasCalledGetThreads = false; server.use( - mockGetThreads(async (_req, res, ctx) => { + mockGetThreads(() => { hasCalledGetThreads = true; - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: now, - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: now, + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -2538,11 +2423,11 @@ describe("useThreads: polling", () => { const { unmount } = render(); - jest.advanceTimersByTime(5 * MINUTES); - await waitFor(() => expect(hasCalledGetThreads).toBe(false)); + vi.advanceTimersByTime(5 * MINUTES); + await vi.waitFor(() => expect(hasCalledGetThreads).toBe(false)); - jest.advanceTimersByTime(5 * MINUTES); - await waitFor(() => expect(hasCalledGetThreads).toBe(false)); + vi.advanceTimersByTime(5 * MINUTES); + await vi.waitFor(() => expect(hasCalledGetThreads).toBe(false)); unmount(); }); @@ -2557,33 +2442,26 @@ describe("WebSocket events", () => { }); server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [], - inboxNotifications: [], - subscriptions: [newThreadSubscription], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [], + inboxNotifications: [], + subscriptions: [newThreadSubscription], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockGetThread({ threadId: newThread.id }, async (_req, res, ctx) => { - return res( - ctx.json({ - thread: newThread, - inboxNotification: undefined, - subscription: newThreadSubscription, - }) - ); + mockGetThread({ threadId: newThread.id }, () => { + return HttpResponse.json({ + thread: newThread, + inboxNotification: undefined, + subscription: newThreadSubscription, + }); }) ); @@ -2599,7 +2477,7 @@ describe("WebSocket events", () => { const sim = await websocketSimulator(); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [], @@ -2615,7 +2493,7 @@ describe("WebSocket events", () => { commentId: newThread.comments[0]!.id, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [newThread], @@ -2636,27 +2514,22 @@ describe("WebSocket events", () => { }); server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [newThread], - inboxNotifications: [], - subscriptions: [newThreadSubscription], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [newThread], + inboxNotifications: [], + subscriptions: [newThreadSubscription], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockGetThread({ threadId: newThread.id }, async (_req, res, ctx) => { - return res(ctx.status(404)); + mockGetThread({ threadId: newThread.id }, () => { + return HttpResponse.json(null, { status: 404 }); }) ); @@ -2672,7 +2545,7 @@ describe("WebSocket events", () => { const sim = await websocketSimulator(); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [newThread], @@ -2689,7 +2562,7 @@ describe("WebSocket events", () => { commentId: newThread.comments[0]!.id, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [], @@ -2710,24 +2583,19 @@ describe("WebSocket events", () => { }); server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [newThread], - inboxNotifications: [], - subscriptions: [newThreadSubscription], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [newThread], + inboxNotifications: [], + subscriptions: [newThreadSubscription], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -2743,7 +2611,7 @@ describe("WebSocket events", () => { const sim = await websocketSimulator(); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [newThread], @@ -2758,7 +2626,7 @@ describe("WebSocket events", () => { threadId: newThread.id, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [], @@ -2793,46 +2661,37 @@ describe("WebSocket events", () => { let callIndex = 0; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockGetThread({ threadId: initialThread.id }, async (_req, res, ctx) => { + mockGetThread({ threadId: initialThread.id }, () => { if (callIndex === 0) { callIndex++; - return res( - ctx.json({ - thread: latestThread, - inboxNotification: undefined, - subscription: dummySubscriptionData({ - subjectId: latestThread.id, - }), - }) - ); + return HttpResponse.json({ + thread: latestThread, + inboxNotification: undefined, + subscription: dummySubscriptionData({ + subjectId: latestThread.id, + }), + }); } else if (callIndex === 1) { callIndex++; - return res( - ctx.json({ - thread: delayedThread, - inboxNotification: undefined, - subscription: undefined, - }) - ); + return HttpResponse.json({ + thread: delayedThread, + inboxNotification: undefined, + subscription: undefined, + }); } else { throw new Error("Only two calls to getThreads are expected"); } @@ -2851,7 +2710,7 @@ describe("WebSocket events", () => { const sim = await websocketSimulator(); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [initialThread], @@ -2873,7 +2732,7 @@ describe("WebSocket events", () => { threadId: initialThread.id, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [latestThread], @@ -2889,11 +2748,11 @@ describe("WebSocket events", () => { describe("useThreadsSuspense", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should fetch threads", async () => { @@ -2904,24 +2763,19 @@ describe("useThreadsSuspense", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -2942,7 +2796,7 @@ describe("useThreadsSuspense", () => { expect(result.current).toEqual(null); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads, @@ -2963,24 +2817,19 @@ describe("useThreadsSuspense", () => { ]; server.use( - mockGetThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - data: threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -3001,7 +2850,7 @@ describe("useThreadsSuspense", () => { expect(result.current).toEqual(null); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads, @@ -3023,12 +2872,12 @@ describe("useThreadsSuspense", () => { describe("useThreadsSuspense: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should trigger error boundary if initial fetch throws an error", async () => { @@ -3036,9 +2885,9 @@ describe("useThreadsSuspense: error", () => { let getThreadsReqCount = 0; server.use( - mockGetThreads((_req, res, ctx) => { + mockGetThreads(() => { getThreadsReqCount++; - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -3073,40 +2922,40 @@ describe("useThreadsSuspense: error", () => { expect(screen.getByText("Loading")).toBeInTheDocument(); // Wait until all fetch attempts have been done - await jest.advanceTimersToNextTimerAsync(); // fetch attempt 1 + await vi.waitFor(() => expect(getThreadsReqCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getThreadsReqCount).toBe(2)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getThreadsReqCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getThreadsReqCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getThreadsReqCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(5)); // Check if the error boundary's fallback is displayed - await waitFor(() => { + await vi.waitFor(() => { expect( screen.getByText("There was an error while getting threads.") ).toBeInTheDocument(); }); // Wait until the error boundary auto-clears - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // Simulate clicking the retry button fireEvent.click(screen.getByText("Retry")); // The error boundary's fallback should be cleared - await waitFor(() => { + await vi.waitFor(() => { expect(screen.getByText("Loading")).toBeInTheDocument(); }); @@ -3151,72 +3000,57 @@ describe("useThreads: pagination", () => { let isPageThreeRequested = false; server.use( - mockGetThreads(async (req, res, ctx) => { - const url = new URL(req.url); + mockGetThreads(({ request }) => { + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); // Request for Page 2 if (cursor === "cursor-1") { isPageTwoRequested = true; - return res( - ctx.json({ - data: threadsPageTwo, - inboxNotifications: [], - subscriptions: subscriptionsPageTwo, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-2", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threadsPageTwo, + inboxNotifications: [], + subscriptions: subscriptionsPageTwo, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-2", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } // Request for Page 3 else if (cursor === "cursor-2") { isPageThreeRequested = true; - return res( - ctx.json({ - data: threadsPageThree, - subscriptions: subscriptionsPageThree, - inboxNotifications: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-3", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threadsPageThree, + subscriptions: subscriptionsPageThree, + inboxNotifications: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-3", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } // Request for the first page else { isPageOneRequested = true; - return res( - ctx.json({ - data: threadsPageOne, - inboxNotifications: [], - subscriptions: subscriptionsPageOne, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threadsPageOne, + inboxNotifications: [], + subscriptions: subscriptionsPageOne, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } }) ); @@ -3234,8 +3068,8 @@ describe("useThreads: pagination", () => { expect(result.current).toEqual({ isLoading: true }); // Initial load (Page 1) - await waitFor(() => expect(isPageOneRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageOneRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne], @@ -3250,8 +3084,8 @@ describe("useThreads: pagination", () => { // Fetch Page 2 fetchMore(); - await waitFor(() => expect(isPageTwoRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageTwoRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne, ...threadsPageTwo], @@ -3264,8 +3098,8 @@ describe("useThreads: pagination", () => { // Fetch Page 3 fetchMore(); - await waitFor(() => expect(isPageThreeRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageThreeRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne, ...threadsPageTwo, ...threadsPageThree], @@ -3305,51 +3139,41 @@ describe("useThreads: pagination", () => { let getThreadsReqCount = 0; server.use( - mockGetThreads(async (req, res, ctx) => { + mockGetThreads(({ request }) => { getThreadsReqCount++; - const url = new URL(req.url); + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); // Request for Page 2 if (cursor === "cursor-1") { isPageTwoRequested = true; - return res( - ctx.json({ - data: threadsPageTwo, - subscriptions: subscriptionsPageTwo, - inboxNotifications: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threadsPageTwo, + subscriptions: subscriptionsPageTwo, + inboxNotifications: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } // Request for the first page else { - return res( - ctx.json({ - data: threadsPageOne, - subscriptions: subscriptionsPageOne, - inboxNotifications: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threadsPageOne, + subscriptions: subscriptionsPageOne, + inboxNotifications: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } }) ); @@ -3366,7 +3190,7 @@ describe("useThreads: pagination", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne], @@ -3381,9 +3205,9 @@ describe("useThreads: pagination", () => { const fetchMore = result.current.fetchMore!; fetchMore(); - await waitFor(() => expect(isPageTwoRequested).toBe(true)); + await vi.waitFor(() => expect(isPageTwoRequested).toBe(true)); expect(getThreadsReqCount).toEqual(2); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne, ...threadsPageTwo], @@ -3405,34 +3229,29 @@ describe("useThreads: pagination", () => { const threadsPageOne = [dummyThreadData({ roomId })]; server.use( - mockGetThreads(async (req, res, ctx) => { - const url = new URL(req.url); + mockGetThreads(({ request }) => { + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); // Initial load (Page 1) if (cursor === null) { - return res( - ctx.json({ - data: threadsPageOne, - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + data: threadsPageOne, + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } // Page 2 else { isPageTwoRequested = true; - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); } }) ); @@ -3450,7 +3269,7 @@ describe("useThreads: pagination", () => { expect(result.current).toEqual({ isLoading: true }); // Initial load (Page 1) - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne], @@ -3466,8 +3285,8 @@ describe("useThreads: pagination", () => { // Fetch Page 2 (which returns an error) fetchMore(); - await waitFor(() => expect(isPageTwoRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageTwoRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: threadsPageOne, diff --git a/packages/liveblocks-react/src/__tests__/useUnreadInboxNotificationsCount.test.tsx b/packages/liveblocks-react/src/__tests__/useUnreadInboxNotificationsCount.test.tsx index ca03dcb6432..cd219e8444c 100644 --- a/packages/liveblocks-react/src/__tests__/useUnreadInboxNotificationsCount.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useUnreadInboxNotificationsCount.test.tsx @@ -1,9 +1,18 @@ -import "@testing-library/jest-dom"; - import { nanoid } from "@liveblocks/core"; -import { renderHook, waitFor } from "@testing-library/react"; +import { renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { Suspense } from "react"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import MockWebSocket from "./_MockWebSocket"; import { mockGetUnreadInboxNotificationsCount } from "./_restMocks"; @@ -27,12 +36,10 @@ afterAll(() => server.close()); describe("useUnreadInboxNotificationsCount", () => { test("should fetch inbox notification count", async () => { server.use( - mockGetUnreadInboxNotificationsCount(async (_req, res, ctx) => { - return res( - ctx.json({ - count: 1, - }) - ); + mockGetUnreadInboxNotificationsCount(() => { + return HttpResponse.json({ + count: 1, + }); }) ); @@ -53,7 +60,7 @@ describe("useUnreadInboxNotificationsCount", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, count: 1, @@ -67,23 +74,19 @@ describe("useUnreadInboxNotificationsCount", () => { const roomA = nanoid(); server.use( - mockGetUnreadInboxNotificationsCount(async (_req, res, ctx) => { - const query = _req.url.searchParams.get("query"); + mockGetUnreadInboxNotificationsCount(({ request }) => { + const query = new URL(request.url).searchParams.get("query"); // For the sake of simplicity, the server mock assumes that if a query is provided, it's for roomA. if (query) { - return res( - ctx.json({ - count: 1, - }) - ); + return HttpResponse.json({ + count: 1, + }); } - return res( - ctx.json({ - count: 2, - }) - ); + return HttpResponse.json({ + count: 2, + }); }) ); @@ -104,7 +107,7 @@ describe("useUnreadInboxNotificationsCount", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, count: 1, @@ -126,7 +129,7 @@ describe("useUnreadInboxNotificationsCount", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result2.current).toEqual({ isLoading: false, count: 2, @@ -140,12 +143,10 @@ describe("useUnreadInboxNotificationsCount", () => { describe("useUnreadInboxNotificationsCount - Suspense", () => { test("should be referentially stable after rerendering", async () => { server.use( - mockGetUnreadInboxNotificationsCount(async (_req, res, ctx) => { - return res( - ctx.json({ - count: 1, - }) - ); + mockGetUnreadInboxNotificationsCount(() => { + return HttpResponse.json({ + count: 1, + }); }) ); @@ -168,7 +169,7 @@ describe("useUnreadInboxNotificationsCount - Suspense", () => { expect(result.current).toEqual(null); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, count: 1, diff --git a/packages/liveblocks-react/src/__tests__/useUnsubscribeFromThread.test.tsx b/packages/liveblocks-react/src/__tests__/useUnsubscribeFromThread.test.tsx index 28239393f4c..62960cb26e1 100644 --- a/packages/liveblocks-react/src/__tests__/useUnsubscribeFromThread.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useUnsubscribeFromThread.test.tsx @@ -1,6 +1,17 @@ import { nanoid, Permission } from "@liveblocks/core"; -import { act, renderHook, waitFor } from "@testing-library/react"; +import { act, renderHook } from "@testing-library/react"; +import { HttpResponse } from "msw"; import { setupServer } from "msw/node"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummySubscriptionData, @@ -36,39 +47,31 @@ describe("useUnsubscribeFromThread", () => { let hasCalledUnsubscribeFromThread = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications, - subscriptions: [ - dummySubscriptionData({ - kind: "thread", - subjectId: initialThread.id, - createdAt: initialThread.createdAt, - }), - ], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications, + subscriptions: [ + dummySubscriptionData({ + kind: "thread", + subjectId: initialThread.id, + createdAt: initialThread.createdAt, + }), + ], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockUnsubscribeFromThread( - { threadId: initialThread.id }, - async (_, res, ctx) => { - hasCalledUnsubscribeFromThread = true; - - return res(ctx.status(200)); - } - ) + mockUnsubscribeFromThread({ threadId: initialThread.id }, () => { + hasCalledUnsubscribeFromThread = true; + + return HttpResponse.json(null, { status: 200 }); + }) ); const { @@ -95,7 +98,7 @@ describe("useUnsubscribeFromThread", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -104,7 +107,9 @@ describe("useUnsubscribeFromThread", () => { act(() => result.current.unsubscribeFromThread(initialThread.id)); - await waitFor(() => expect(hasCalledUnsubscribeFromThread).toEqual(true)); + await vi.waitFor(() => + expect(hasCalledUnsubscribeFromThread).toEqual(true) + ); // The thread should optimistically no longer be subscribed to expect(result.current.subscription.status).toBe("not-subscribed"); @@ -118,33 +123,25 @@ describe("useUnsubscribeFromThread", () => { let hasCalledUnsubscribeFromThread = false; server.use( - mockGetThreads((_req, res, ctx) => { - return res( - ctx.json({ - data: [initialThread], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetThreads(() => { + return HttpResponse.json({ + data: [initialThread], + inboxNotifications: [], + subscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }), - mockUnsubscribeFromThread( - { threadId: initialThread.id }, - async (_, res, ctx) => { - hasCalledUnsubscribeFromThread = true; - - return res(ctx.status(200)); - } - ) + mockUnsubscribeFromThread({ threadId: initialThread.id }, () => { + hasCalledUnsubscribeFromThread = true; + + return HttpResponse.json(null, { status: 200 }); + }) ); const { @@ -171,7 +168,7 @@ describe("useUnsubscribeFromThread", () => { expect(result.current.threads).toBeUndefined(); - await waitFor(() => + await vi.waitFor(() => expect(result.current.threads).toEqual([initialThread]) ); @@ -179,7 +176,9 @@ describe("useUnsubscribeFromThread", () => { act(() => result.current.unsubscribeFromThread(initialThread.id)); - await waitFor(() => expect(hasCalledUnsubscribeFromThread).toEqual(true)); + await vi.waitFor(() => + expect(hasCalledUnsubscribeFromThread).toEqual(true) + ); expect(result.current.subscription.status).toBe("not-subscribed"); diff --git a/packages/liveblocks-react/src/__tests__/useUrlMetadata.test.tsx b/packages/liveblocks-react/src/__tests__/useUrlMetadata.test.tsx index bb12ebe914c..b86a8464199 100644 --- a/packages/liveblocks-react/src/__tests__/useUrlMetadata.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useUrlMetadata.test.tsx @@ -1,12 +1,20 @@ -import "@testing-library/jest-dom"; - import type { UrlMetadata } from "@liveblocks/core"; -import { renderHook, screen, waitFor } from "@testing-library/react"; -import type { ResponseResolver, RestContext, RestRequest } from "msw"; -import { rest } from "msw"; +import { renderHook, screen } from "@testing-library/react"; +import type { HttpResponseResolver } from "msw"; +import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import MockWebSocket from "./_MockWebSocket"; import { createContextsForTest } from "./_utils"; @@ -22,29 +30,29 @@ beforeEach(() => { afterEach(() => { MockWebSocket.reset(); server.resetHandlers(); - jest.clearAllTimers(); - jest.clearAllMocks(); + vi.clearAllTimers(); + vi.clearAllMocks(); }); afterAll(() => server.close()); function mockGetUrlMetadata( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { url: string }, + never, { metadata: UrlMetadata } > ) { - return rest.get("https://api.liveblocks.io/v2/c/urls/metadata", resolver); + return http.get("https://api.liveblocks.io/v2/c/urls/metadata", resolver); } describe("useUrlMetadata", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should fetch URL metadata", async () => { @@ -58,9 +66,9 @@ describe("useUrlMetadata", () => { }; server.use( - mockGetUrlMetadata((req, res, ctx) => { - expect(req.url.searchParams.get("url")).toBe(url); - return res(ctx.json({ metadata })); + mockGetUrlMetadata(({ request }) => { + expect(new URL(request.url).searchParams.get("url")).toBe(url); + return HttpResponse.json({ metadata }); }) ); @@ -78,7 +86,7 @@ describe("useUrlMetadata", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, metadata, @@ -96,9 +104,9 @@ describe("useUrlMetadata", () => { let fetchCount = 0; server.use( - mockGetUrlMetadata((_req, res, ctx) => { + mockGetUrlMetadata(() => { fetchCount++; - return res(ctx.json({ metadata })); + return HttpResponse.json({ metadata }); }) ); @@ -115,7 +123,7 @@ describe("useUrlMetadata", () => { } ); - await waitFor(() => expect(result.current.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.isLoading).toBeFalsy()); expect(result.current).toEqual({ isLoading: false, @@ -149,14 +157,14 @@ describe("useUrlMetadata", () => { }; server.use( - mockGetUrlMetadata((req, res, ctx) => { - const requestedUrl = req.url.searchParams.get("url"); + mockGetUrlMetadata(({ request }) => { + const requestedUrl = new URL(request.url).searchParams.get("url"); if (requestedUrl === url1) { - return res(ctx.json({ metadata: metadata1 })); + return HttpResponse.json({ metadata: metadata1 }); } else if (requestedUrl === url2) { - return res(ctx.json({ metadata: metadata2 })); + return HttpResponse.json({ metadata: metadata2 }); } - return res(ctx.status(404)); + return HttpResponse.json(null, { status: 404 }); }) ); @@ -174,7 +182,7 @@ describe("useUrlMetadata", () => { } ); - await waitFor(() => expect(result.current.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.isLoading).toBeFalsy()); expect(result.current).toEqual({ isLoading: false, @@ -188,7 +196,7 @@ describe("useUrlMetadata", () => { isLoading: true, }); - await waitFor(() => expect(result.current.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.isLoading).toBeFalsy()); expect(result.current).toEqual({ isLoading: false, @@ -213,15 +221,15 @@ describe("useUrlMetadata", () => { let fetchCount = 0; server.use( - mockGetUrlMetadata((_req, res, ctx) => { + mockGetUrlMetadata(({ request }) => { fetchCount++; - const requestedUrl = _req.url.searchParams.get("url"); + const requestedUrl = new URL(request.url).searchParams.get("url"); if (requestedUrl === url1) { - return res(ctx.json({ metadata: metadata1 })); + return HttpResponse.json({ metadata: metadata1 }); } else if (requestedUrl === url2) { - return res(ctx.json({ metadata: metadata2 })); + return HttpResponse.json({ metadata: metadata2 }); } - return res(ctx.status(404)); + return HttpResponse.json(null, { status: 404 }); }) ); @@ -239,11 +247,11 @@ describe("useUrlMetadata", () => { } ); - await waitFor(() => expect(result.current.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.isLoading).toBeFalsy()); // Change to URL 2 rerender({ url: url2 }); - await waitFor(() => expect(result.current.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.isLoading).toBeFalsy()); // Change back to URL 1 - should use cached data rerender({ url: url1 }); @@ -267,9 +275,9 @@ describe("useUrlMetadata", () => { let fetchCount = 0; server.use( - mockGetUrlMetadata((_req, res, ctx) => { + mockGetUrlMetadata(() => { fetchCount++; - return res(ctx.json({ metadata })); + return HttpResponse.json({ metadata }); }) ); @@ -290,7 +298,7 @@ describe("useUrlMetadata", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.metadata1.isLoading).toBeFalsy(); expect(result.current.metadata2.isLoading).toBeFalsy(); expect(result.current.metadata3.isLoading).toBeFalsy(); @@ -321,8 +329,8 @@ describe("useUrlMetadata", () => { const url = "https://github.com"; server.use( - mockGetUrlMetadata((_req, res, ctx) => { - return res(ctx.status(500)); + mockGetUrlMetadata(() => { + return HttpResponse.json(null, { status: 500 }); }) ); @@ -339,9 +347,9 @@ describe("useUrlMetadata", () => { expect(result.current).toEqual({ isLoading: true }); // Wait until all fetch attempts have been done - await jest.advanceTimersToNextTimerAsync(); // fetch attempt 1 + await vi.advanceTimersToNextTimerAsync(); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current).toEqual({ isLoading: false, error: expect.any(Error), @@ -354,11 +362,11 @@ describe("useUrlMetadata", () => { describe("useUrlMetadataSuspense", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should suspend while fetching URL metadata", async () => { @@ -372,8 +380,8 @@ describe("useUrlMetadataSuspense", () => { }; server.use( - mockGetUrlMetadata((_req, res, ctx) => { - return res(ctx.json({ metadata })); + mockGetUrlMetadata(() => { + return HttpResponse.json({ metadata }); }) ); @@ -393,7 +401,7 @@ describe("useUrlMetadataSuspense", () => { expect(result.current).toEqual(null); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, metadata, @@ -407,8 +415,8 @@ describe("useUrlMetadataSuspense", () => { const url = "https://github.com"; server.use( - mockGetUrlMetadata((_req, res, ctx) => { - return res(ctx.status(500)); + mockGetUrlMetadata(() => { + return HttpResponse.json(null, { status: 500 }); }) ); @@ -435,7 +443,7 @@ describe("useUrlMetadataSuspense", () => { expect(screen.getByText("Loading")).toBeInTheDocument(); // Check if the error boundary's fallback is displayed - await waitFor(() => { + await vi.waitFor(() => { expect( screen.getByText("There was an error while getting URL metadata.") ).toBeInTheDocument(); @@ -458,14 +466,14 @@ describe("useUrlMetadataSuspense", () => { }; server.use( - mockGetUrlMetadata((_req, res, ctx) => { - const requestedUrl = _req.url.searchParams.get("url"); + mockGetUrlMetadata(({ request }) => { + const requestedUrl = new URL(request.url).searchParams.get("url"); if (requestedUrl === url1) { - return res(ctx.json({ metadata: metadata1 })); + return HttpResponse.json({ metadata: metadata1 }); } else if (requestedUrl === url2) { - return res(ctx.json({ metadata: metadata2 })); + return HttpResponse.json({ metadata: metadata2 }); } - return res(ctx.status(404)); + return HttpResponse.json(null, { status: 404 }); }) ); @@ -487,7 +495,7 @@ describe("useUrlMetadataSuspense", () => { } ); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, metadata: metadata1, @@ -496,7 +504,7 @@ describe("useUrlMetadataSuspense", () => { // Change to URL 2 rerender({ url: url2 }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, metadata: metadata2, diff --git a/packages/liveblocks-react/src/__tests__/useUser.test.tsx b/packages/liveblocks-react/src/__tests__/useUser.test.tsx index a4f4391fe35..33abd30c1c1 100644 --- a/packages/liveblocks-react/src/__tests__/useUser.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useUser.test.tsx @@ -1,25 +1,23 @@ -import "@testing-library/jest-dom"; - import type { ResolveUsersArgs } from "@liveblocks/core"; import { nanoid } from "@liveblocks/core"; -import { renderHook, screen, waitFor } from "@testing-library/react"; +import { renderHook, screen } from "@testing-library/react"; import { Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { afterAll, beforeAll, describe, expect, test, vi } from "vitest"; import { act, createContextsForTest } from "./_utils"; -// eslint-disable-next-line @typescript-eslint/require-await async function defaultResolveUsers({ userIds }: ResolveUsersArgs) { return userIds.map((userId) => ({ name: userId })); } describe("useUser", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should return an error if resolveUsers is not set", async () => { @@ -44,7 +42,7 @@ describe("useUser", () => { expect(result.current.user).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); expect(result.current.user).toEqual({ isLoading: false, @@ -76,7 +74,7 @@ describe("useUser", () => { expect(result.current.user).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); expect(result.current.user).toEqual({ isLoading: false, @@ -109,7 +107,7 @@ describe("useUser", () => { expect(result.current.user).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); expect(result.current.user).toEqual({ isLoading: false, @@ -120,7 +118,7 @@ describe("useUser", () => { expect(result.current.user).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); expect(result.current.user).toEqual({ isLoading: false, @@ -133,7 +131,7 @@ describe("useUser", () => { test("should cache results based on user ID", async () => { const roomId = nanoid(); - const resolveUsers = jest.fn(({ userIds }: ResolveUsersArgs) => + const resolveUsers = vi.fn(({ userIds }: ResolveUsersArgs) => userIds.map((userId) => ({ name: userId })) ); const { @@ -154,11 +152,11 @@ describe("useUser", () => { } ); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); rerender({ userId: "123" }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); rerender({ userId: "abc" }); @@ -179,7 +177,7 @@ describe("useUser", () => { test("should revalidate instantly if its cache is invalidated", async () => { const roomId = nanoid(); - const resolveUsers = jest.fn(({ userIds }: ResolveUsersArgs) => + const resolveUsers = vi.fn(({ userIds }: ResolveUsersArgs) => userIds.map((userId) => ({ name: userId })) ); const { @@ -201,11 +199,11 @@ describe("useUser", () => { } ); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); rerender({ userId: "123" }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); rerender({ userId: "abc" }); @@ -217,7 +215,7 @@ describe("useUser", () => { // Invalidate all user IDs act(() => client.resolvers.invalidateUsers()); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); expect(result.current.user).toEqual({ isLoading: false, @@ -238,7 +236,7 @@ describe("useUser", () => { test("should batch (and deduplicate) requests for the same user ID", async () => { const roomId = nanoid(); - const resolveUsers = jest.fn(({ userIds }: ResolveUsersArgs) => + const resolveUsers = vi.fn(({ userIds }: ResolveUsersArgs) => userIds.map((userId) => ({ name: userId })) ); const { @@ -260,7 +258,7 @@ describe("useUser", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.userAbc.isLoading).toBeFalsy(); expect(result.current.userAbc2.isLoading).toBeFalsy(); expect(result.current.user123.isLoading).toBeFalsy(); @@ -311,7 +309,7 @@ describe("useUser", () => { expect(result.current.user).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); expect(result.current.user).toEqual({ isLoading: false, @@ -344,7 +342,7 @@ describe("useUser", () => { expect(result.current.user).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); expect(result.current.user).toEqual({ isLoading: false, @@ -377,7 +375,7 @@ describe("useUser", () => { expect(result.current.user).toEqual({ isLoading: true }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); expect(result.current.user).toEqual({ isLoading: false, @@ -390,7 +388,7 @@ describe("useUser", () => { test("should return an error if resolveUsers returns undefined for a specifc user ID", async () => { const roomId = nanoid(); - const resolveUsers = jest.fn(({ userIds }: ResolveUsersArgs) => + const resolveUsers = vi.fn(({ userIds }: ResolveUsersArgs) => userIds.map((userId) => { if (userId === "abc") { return undefined; @@ -416,7 +414,7 @@ describe("useUser", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current.userAbc.isLoading).toBeFalsy(); expect(result.current.user123.isLoading).toBeFalsy(); }); @@ -439,11 +437,11 @@ describe("useUser", () => { describe("useUserSuspense", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should suspend with Suspense", async () => { @@ -474,14 +472,14 @@ describe("useUserSuspense", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is displayed expect(screen.getByText("Loading")).toBeInTheDocument(); }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is no longer displayed expect(screen.getByText("Loaded")).toBeInTheDocument(); }); @@ -518,28 +516,28 @@ describe("useUserSuspense", () => { } ); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is displayed expect(screen.getByText("Loading")).toBeInTheDocument(); }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is no longer displayed expect(screen.getByText("Loaded")).toBeInTheDocument(); }); act(() => client.resolvers.invalidateUsers()); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is displayed again expect(screen.getByText("Loading")).toBeInTheDocument(); }); - await waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); + await vi.waitFor(() => expect(result.current.user.isLoading).toBeFalsy()); - await waitFor(() => { + await vi.waitFor(() => { // Check if the Suspense fallback is no longer displayed again expect(screen.getByText("Loaded")).toBeInTheDocument(); }); @@ -580,7 +578,7 @@ describe("useUserSuspense", () => { expect(result.current).toEqual(null); - await waitFor(() => { + await vi.waitFor(() => { // Check if the error boundary fallback is displayed expect( screen.getByText("There was an error while getting user.") diff --git a/packages/liveblocks-react/src/__tests__/useUserThreads.test.tsx b/packages/liveblocks-react/src/__tests__/useUserThreads.test.tsx index 42a7145a73e..e65fe9e3173 100644 --- a/packages/liveblocks-react/src/__tests__/useUserThreads.test.tsx +++ b/packages/liveblocks-react/src/__tests__/useUserThreads.test.tsx @@ -1,5 +1,3 @@ -import "@testing-library/jest-dom"; - import type { InboxNotificationData, SubscriptionData, @@ -7,12 +5,22 @@ import type { ThreadDataWithDeleteInfo, } from "@liveblocks/core"; import { HttpError, nanoid, Permission } from "@liveblocks/core"; -import { fireEvent, renderHook, screen, waitFor } from "@testing-library/react"; -import type { ResponseResolver, RestContext, RestRequest } from "msw"; -import { rest } from "msw"; +import { fireEvent, renderHook, screen } from "@testing-library/react"; +import type { HttpResponseResolver } from "msw"; +import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + vi, +} from "vitest"; import { dummySubscriptionData, dummyThreadData } from "./_dummies"; import MockWebSocket from "./_MockWebSocket"; @@ -29,16 +37,16 @@ beforeEach(() => { afterEach(() => { MockWebSocket.reset(); server.resetHandlers(); - jest.clearAllTimers(); - jest.clearAllMocks(); + vi.clearAllTimers(); + vi.clearAllMocks(); }); afterAll(() => server.close()); function mockGetUserThreads( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + never, { threads: ThreadData[]; inboxNotifications: InboxNotificationData[]; @@ -51,13 +59,13 @@ function mockGetUserThreads( } > ) { - return rest.get("https://api.liveblocks.io/v2/c/threads", resolver); + return http.get("https://api.liveblocks.io/v2/c/threads", resolver); } function mockGetUserThreadsDelta( - resolver: ResponseResolver< - RestRequest, - RestContext, + resolver: HttpResponseResolver< + { roomId: string }, + never, { threads: ThreadData[]; inboxNotifications: InboxNotificationData[]; @@ -72,16 +80,16 @@ function mockGetUserThreadsDelta( } > ) { - return rest.get("https://api.liveblocks.io/v2/c/threads/delta", resolver); + return http.get("https://api.liveblocks.io/v2/c/threads/delta", resolver); } describe("useUserThreads", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should fetch user threads on mount", async () => { @@ -92,21 +100,19 @@ describe("useUserThreads", () => { ]; server.use( - mockGetUserThreads((_req, res, ctx) => { - return res( - ctx.json({ - threads, - inboxNotifications: [], - subscriptions, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetUserThreads(() => { + return HttpResponse.json({ + threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -127,7 +133,7 @@ describe("useUserThreads", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads, @@ -161,24 +167,22 @@ describe("useUserThreads", () => { ]; server.use( - mockGetUserThreads((req, res, ctx) => { - const url = new URL(req.url); + mockGetUserThreads(({ request }) => { + const url = new URL(request.url); const query = url.searchParams.get("query"); const pred = query ? makeThreadFilter(query) : () => true; - return res( - ctx.json({ - threads: [pinnedThread, unpinnedThread].filter(pred), - inboxNotifications: [], - subscriptions, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + threads: [pinnedThread, unpinnedThread].filter(pred), + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -200,7 +204,7 @@ describe("useUserThreads", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [pinnedThread], @@ -232,21 +236,19 @@ describe("useUserThreads", () => { ]; server.use( - mockGetUserThreads((_req, res, ctx) => { - return res( - ctx.json({ - threads: [latestUpdatedThread, earliestUpdatedThread], - inboxNotifications: [], - subscriptions, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetUserThreads(() => { + return HttpResponse.json({ + threads: [latestUpdatedThread, earliestUpdatedThread], + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -267,7 +269,7 @@ describe("useUserThreads", () => { isLoading: true, }); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [latestUpdatedThread, earliestUpdatedThread], @@ -284,21 +286,21 @@ describe("useUserThreads", () => { describe("useThreads: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should trigger error boundary if initial fetch throws an error", async () => { let getThreadsReqCount = 0; server.use( - mockGetUserThreads((_req, res, ctx) => { + mockGetUserThreads(() => { getThreadsReqCount++; - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }) ); @@ -318,26 +320,26 @@ describe("useThreads: error", () => { expect(result.current).toEqual({ isLoading: true }); // Wait until all fetch attempts have been done - await jest.advanceTimersToNextTimerAsync(); // fetch attempt 1 + await vi.waitFor(() => expect(getThreadsReqCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getThreadsReqCount).toBe(2)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getThreadsReqCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getThreadsReqCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getThreadsReqCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(5)); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current).toEqual({ isLoading: false, error: expect.any(Error), @@ -345,17 +347,17 @@ describe("useThreads: error", () => { }); // Wait for 5 second for the error to clear - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the initial render - await waitFor(() => expect(getThreadsReqCount).toBe(6)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(6)); expect(result.current).toEqual({ isLoading: true, }); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getThreadsReqCount).toBe(7)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(7)); // and so on... @@ -364,9 +366,9 @@ describe("useThreads: error", () => { test("should not retry if a 403 Forbidden response is received from server", async () => { server.use( - mockGetUserThreads((_req, res, ctx) => { + mockGetUserThreads(() => { // Return a 403 status from the server for the initial fetch - return res(ctx.status(403)); + return HttpResponse.json(null, { status: 403 }); }) ); @@ -385,7 +387,7 @@ describe("useThreads: error", () => { expect(result.current).toEqual({ isLoading: true }); - await waitFor(() => { + await vi.waitFor(() => { expect(result.current).toEqual({ isLoading: false, error: expect.any(HttpError), @@ -398,11 +400,11 @@ describe("useThreads: error", () => { describe("useThreadsSuspense", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should fetch user threads on render", async () => { @@ -413,24 +415,19 @@ describe("useThreadsSuspense", () => { ]; server.use( - mockGetUserThreads(async (_req, res, ctx) => { - return res( - ctx.json({ - threads, - inboxNotifications: [], - subscriptions, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + mockGetUserThreads(() => { + return HttpResponse.json({ + threads, + inboxNotifications: [], + subscriptions, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); }) ); @@ -453,7 +450,7 @@ describe("useThreadsSuspense", () => { expect(result.current).toEqual(null); - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads, @@ -469,37 +466,35 @@ describe("useThreadsSuspense", () => { describe("useUserThreadsSuspense: error", () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); // Restores the real timers + vi.clearAllTimers(); + vi.useRealTimers(); // Restores the real timers }); test("should trigger error boundary if initial fetch throws an error", async () => { let getThreadsReqCount = 0; server.use( - mockGetUserThreads((_req, res, ctx) => { + mockGetUserThreads(() => { getThreadsReqCount++; - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); }), - mockGetUserThreadsDelta((_req, res, ctx) => { - return res( - ctx.json({ - threads: [], - inboxNotifications: [], - subscriptions: [], - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - permissionHints: {}, - }, - }) - ); + mockGetUserThreadsDelta(() => { + return HttpResponse.json({ + threads: [], + inboxNotifications: [], + subscriptions: [], + deletedThreads: [], + deletedInboxNotifications: [], + deletedSubscriptions: [], + meta: { + requestedAt: new Date().toISOString(), + permissionHints: {}, + }, + }); }) ); @@ -536,40 +531,40 @@ describe("useUserThreadsSuspense: error", () => { expect(screen.getByText("Loading")).toBeInTheDocument(); // Wait until all fetch attempts have been done - await jest.advanceTimersToNextTimerAsync(); // fetch attempt 1 + await vi.waitFor(() => expect(getThreadsReqCount).toBe(1)); // The first retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // A new fetch request for the threads should have been made after the first retry - await waitFor(() => expect(getThreadsReqCount).toBe(2)); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(2)); // The second retry should be made after 5s - await jest.advanceTimersByTimeAsync(5_000); - await waitFor(() => expect(getThreadsReqCount).toBe(3)); + await vi.advanceTimersByTimeAsync(5_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(3)); // The third retry should be made after 10s - await jest.advanceTimersByTimeAsync(10_000); - await waitFor(() => expect(getThreadsReqCount).toBe(4)); + await vi.advanceTimersByTimeAsync(10_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(4)); // The fourth retry should be made after 15s - await jest.advanceTimersByTimeAsync(15_000); - await waitFor(() => expect(getThreadsReqCount).toBe(5)); + await vi.advanceTimersByTimeAsync(15_000); + await vi.waitFor(() => expect(getThreadsReqCount).toBe(5)); // Check if the error boundary's fallback is displayed - await waitFor(() => { + await vi.waitFor(() => { expect( screen.getByText("There was an error while getting threads.") ).toBeInTheDocument(); }); // Wait until the error boundary auto-clears - await jest.advanceTimersByTimeAsync(5_000); + await vi.advanceTimersByTimeAsync(5_000); // Simulate clicking the retry button fireEvent.click(screen.getByText("Retry")); // The error boundary's fallback should be cleared - await waitFor(() => { + await vi.waitFor(() => { expect(screen.getByText("Loading")).toBeInTheDocument(); }); @@ -579,11 +574,11 @@ describe("useUserThreadsSuspense: error", () => { describe("useUserThreads: pagination", () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test("should load the next page of data when `fetchMore` is called", async () => { @@ -612,63 +607,57 @@ describe("useUserThreads: pagination", () => { let isPageThreeRequested = false; server.use( - mockGetUserThreads((req, res, ctx) => { - const url = new URL(req.url); + mockGetUserThreads(({ request }) => { + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); // Request for Page 2 if (cursor === "cursor-1") { isPageTwoRequested = true; - return res( - ctx.json({ - threads: threadsPageTwo, - inboxNotifications: [], - subscriptions: subscriptionsPageTwo, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-2", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + threads: threadsPageTwo, + inboxNotifications: [], + subscriptions: subscriptionsPageTwo, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-2", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } // Request for Page 3 else if (cursor === "cursor-2") { isPageThreeRequested = true; - return res( - ctx.json({ - threads: threadsPageThree, - inboxNotifications: [], - subscriptions: subscriptionsPageThree, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-3", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + threads: threadsPageThree, + inboxNotifications: [], + subscriptions: subscriptionsPageThree, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-3", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } // Request for Page 1 else { isPageOneRequested = true; - return res( - ctx.json({ - threads: threadsPageOne, - inboxNotifications: [], - subscriptions: subscriptionsPageOne, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + threads: threadsPageOne, + inboxNotifications: [], + subscriptions: subscriptionsPageOne, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } }) ); @@ -691,8 +680,8 @@ describe("useUserThreads: pagination", () => { }); // Initial load (Page 1) - await waitFor(() => expect(isPageOneRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageOneRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne], @@ -707,8 +696,8 @@ describe("useUserThreads: pagination", () => { // Fetch Page 2 fetchMore(); - await waitFor(() => expect(isPageTwoRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageTwoRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne, ...threadsPageTwo], @@ -721,8 +710,8 @@ describe("useUserThreads: pagination", () => { // Fetch Page 3 fetchMore(); - await waitFor(() => expect(isPageThreeRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageThreeRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne, ...threadsPageTwo, ...threadsPageThree], @@ -755,45 +744,41 @@ describe("useUserThreads: pagination", () => { let isPageTwoRequested = false; server.use( - mockGetUserThreads((req, res, ctx) => { - const url = new URL(req.url); + mockGetUserThreads(({ request }) => { + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); // Request for Page 2 if (cursor === "cursor-1") { isPageTwoRequested = true; - return res( - ctx.json({ - threads: threadsPageTwo, - inboxNotifications: [], - subscriptions: subscriptionsPageTwo, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: null, - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + threads: threadsPageTwo, + inboxNotifications: [], + subscriptions: subscriptionsPageTwo, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: null, + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } // Request for Page 1 else { isPageOneRequested = true; - return res( - ctx.json({ - threads: threadsPageOne, - inboxNotifications: [], - subscriptions: subscriptionsPageOne, - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + threads: threadsPageOne, + inboxNotifications: [], + subscriptions: subscriptionsPageOne, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } }) ); @@ -816,8 +801,8 @@ describe("useUserThreads: pagination", () => { }); // Initial load (Page 1) - await waitFor(() => expect(isPageOneRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageOneRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne], @@ -832,8 +817,8 @@ describe("useUserThreads: pagination", () => { // Fetch Page 2 fetchMore(); - await waitFor(() => expect(isPageTwoRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageTwoRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne, ...threadsPageTwo], @@ -856,34 +841,29 @@ describe("useUserThreads: pagination", () => { let isPageTwoRequested = false; server.use( - mockGetUserThreads((req, res, ctx) => { - const url = new URL(req.url); + mockGetUserThreads(({ request }) => { + const url = new URL(request.url); const cursor = url.searchParams.get("cursor"); // Initial load (Page 1) if (cursor === null) { - return res( - ctx.json({ - threads: threadsPageOne, - inboxNotifications: [], - subscriptions: subscriptionsPageOne, - deletedThreads: [], - deletedInboxNotifications: [], - deletedSubscriptions: [], - meta: { - requestedAt: new Date().toISOString(), - nextCursor: "cursor-1", - permissionHints: { - [roomId]: [Permission.Write], - }, + return HttpResponse.json({ + threads: threadsPageOne, + inboxNotifications: [], + subscriptions: subscriptionsPageOne, + meta: { + requestedAt: new Date().toISOString(), + nextCursor: "cursor-1", + permissionHints: { + [roomId]: [Permission.Write], }, - }) - ); + }, + }); } // Page 2 else { isPageTwoRequested = true; - return res(ctx.status(500)); + return HttpResponse.json(null, { status: 500 }); } }) ); @@ -904,7 +884,7 @@ describe("useUserThreads: pagination", () => { expect(result.current).toEqual({ isLoading: true }); // Initial load (Page 1) - await waitFor(() => + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: [...threadsPageOne], @@ -920,8 +900,8 @@ describe("useUserThreads: pagination", () => { // Fetch Page 2 (which returns an error) fetchMore(); - await waitFor(() => expect(isPageTwoRequested).toBe(true)); - await waitFor(() => + await vi.waitFor(() => expect(isPageTwoRequested).toBe(true)); + await vi.waitFor(() => expect(result.current).toEqual({ isLoading: false, threads: threadsPageOne, diff --git a/packages/liveblocks-react/vitest.config.ts b/packages/liveblocks-react/vitest.config.ts new file mode 100644 index 00000000000..7764194ace7 --- /dev/null +++ b/packages/liveblocks-react/vitest.config.ts @@ -0,0 +1,19 @@ +import { defaultLiveblocksVitestConfig } from "@liveblocks/vitest-config"; + +export default defaultLiveblocksVitestConfig({ + test: { + setupFiles: ["vitest.setup.ts"], + environment: "jsdom", + environmentOptions: { + jsdom: { + url: "http://dummy/", + }, + }, + + // Collect code coverage for this project, when using the --coverage flag + coverage: { + provider: "istanbul", + exclude: ["**/__tests__/**"], + }, + }, +}); diff --git a/packages/liveblocks-react/vitest.setup.ts b/packages/liveblocks-react/vitest.setup.ts new file mode 100644 index 00000000000..910e4704b99 --- /dev/null +++ b/packages/liveblocks-react/vitest.setup.ts @@ -0,0 +1,8 @@ +import "@testing-library/jest-dom/vitest"; +import { afterEach } from "vitest"; +import { cleanup } from "@testing-library/react"; + +// `@testing-library/react` only auto-registers `cleanup()` when using globals. +afterEach(() => { + cleanup(); +}); diff --git a/shared/jest-config/fetch-polyfill.js b/shared/jest-config/fetch-polyfill.js deleted file mode 100644 index 6ced05b9ffa..00000000000 --- a/shared/jest-config/fetch-polyfill.js +++ /dev/null @@ -1,6 +0,0 @@ -// -// NOTE: Node, which runs our Jest tests, does not have a global window.fetch -// API. By including the following line before each test run, we polyfill it, -// since Liveblocks relies on it internally. -// -require("whatwg-fetch"); diff --git a/shared/jest-config/index.js b/shared/jest-config/index.js deleted file mode 100644 index 50b7b35d60b..00000000000 --- a/shared/jest-config/index.js +++ /dev/null @@ -1,41 +0,0 @@ -/** @type {import('jest').Config} */ - -/** - * Standard Jest configuration, used by all projects in this monorepo. - */ -module.exports = { - // By default, assume Jest will be used in a DOM environment. If you need to - // use "node", you can overwrite it in the project. - testEnvironment: "jsdom", - - preset: "ts-jest", - - // NOTE: See https://github.com/kulshekhar/ts-jest/issues/4081#issuecomment-1503684089 - transform: { - ".tsx?": [ - "ts-jest", - { - // Note: We shouldn't need to include `isolatedModules` here because it's a deprecated config option in TS 5, - // but setting it to `true` fixes the `ESM syntax is not allowed in a CommonJS module when - // 'verbatimModuleSyntax' is enabled` error that we're seeing when running our Jest tests. - isolatedModules: true, - useESM: true, - }, - ], - }, - - modulePathIgnorePatterns: ["/dist/"], - testPathIgnorePatterns: ["__tests__/_.*", "__tests__/(.+/)*_.*"], - roots: ["/src"], - - // Jest by default still assumes CJS imports, even if the package uses `type: - // "module"`. These two settings tell Jest that, yes, really, we want to use - // ESM imports. But really, we should switch to Vitest everywhere. - extensionsToTreatAsEsm: [".ts", ".tsx"], - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.jsx?$": "$1", - }, - - // Ensure `window.fetch` is polyfilled if it isn't available in the runtime - setupFiles: ["@liveblocks/jest-config/fetch-polyfill"], -}; diff --git a/shared/jest-config/package.json b/shared/jest-config/package.json deleted file mode 100644 index 85f5a214198..00000000000 --- a/shared/jest-config/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@liveblocks/jest-config", - "private": true, - "main": "index.js", - "dependencies": { - "@types/jest": "^29.5.14", - "fast-check": "^4.3.0", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", - "ts-jest": "^29.2.5", - "whatwg-fetch": "^3.6.20" - } -} diff --git a/tools/liveblocks-codemod/jest.config.js b/tools/liveblocks-codemod/jest.config.js deleted file mode 100644 index 2b4b5a4a80e..00000000000 --- a/tools/liveblocks-codemod/jest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -/** @type {import('jest').Config} */ - -const commonJestConfig = require("@liveblocks/jest-config"); - -module.exports = { - // Our standard Jest configuration, used by all projects in this monorepo - ...commonJestConfig, - - // Collect code coverage for this project, when using the --coverage flag - coveragePathIgnorePatterns: ["/__tests__/"], -}; diff --git a/tools/liveblocks-codemod/package.json b/tools/liveblocks-codemod/package.json index bd47c066708..2d13cbffde9 100644 --- a/tools/liveblocks-codemod/package.json +++ b/tools/liveblocks-codemod/package.json @@ -3,7 +3,6 @@ "version": "2.20240816.0", "description": "Codemods for updating Liveblocks apps.", "license": "Apache-2.0", - "author": "Liveblocks Inc.", "bin": "dist/bin/liveblocks-codemod.js", "files": [ "dist/**", @@ -14,13 +13,12 @@ "build": "tsc -d --project ./tsconfig.json", "format": "(eslint --fix src/ || true) && prettier --write src/", "lint": "eslint src/", - "test": "jest --silent --verbose --color=always", - "test:ci": "jest --silent --verbose --color=always", - "test:watch": "jest --silent --verbose --color=always --watch" + "test": "vitest run", + "test:watch": "vitest" }, "devDependencies": { "@liveblocks/eslint-config": "*", - "@liveblocks/jest-config": "*", + "@liveblocks/vitest-config": "*", "@types/is-git-clean": "1.1.2", "@types/jscodeshift": "0.11.11" }, diff --git a/tools/liveblocks-codemod/src/replacements/__tests__/react-comments-to-react-ui.ts.test.ts b/tools/liveblocks-codemod/src/replacements/__tests__/react-comments-to-react-ui.ts.test.ts index 69f01951eb7..1fa00a6356a 100644 --- a/tools/liveblocks-codemod/src/replacements/__tests__/react-comments-to-react-ui.ts.test.ts +++ b/tools/liveblocks-codemod/src/replacements/__tests__/react-comments-to-react-ui.ts.test.ts @@ -1,7 +1,8 @@ +import { describe, test, expect } from "vitest"; import { replaceReactCommentsImportsInCss } from "../react-comments-to-react-ui"; describe("replaceReactCommentsInPackageJson", () => { - it("should update CSS imports with double quotes", () => { + test("should update CSS imports with double quotes", () => { const input = ` @import "./globals.css"; @import "@liveblocks/react-comments/styles.css"; @@ -26,7 +27,7 @@ body::before { expect(replaceReactCommentsImportsInCss(input)).toEqual(output); }); - it("should update CSS imports with single quotes", () => { + test("should update CSS imports with single quotes", () => { const input = ` @import './globals.css'; @import '@liveblocks/react-comments/styles.css'; diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/from-core.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/from-core.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/from-core.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/from-core.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/from-core.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/from-core.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/from-core.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/from-core.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/general.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/general.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/general.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/general.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/general.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/general.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/general.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/general.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/renamed-local.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/renamed-local.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/renamed-local.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/renamed-local.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/renamed-local.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/renamed-local.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/renamed-local.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/renamed-local.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/unrelated.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/unrelated.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/unrelated.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/unrelated.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/unrelated.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/unrelated.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/live-list-constructor/unrelated.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/live-list-constructor/unrelated.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/liveblocks-ui-config/general.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/liveblocks-ui-config/general.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/liveblocks-ui-config/general.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/liveblocks-ui-config/general.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/liveblocks-ui-config/general.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/liveblocks-ui-config/general.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/liveblocks-ui-config/general.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/liveblocks-ui-config/general.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/react-comments-to-react-ui/general.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/react-comments-to-react-ui/general.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/react-comments-to-react-ui/general.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/react-comments-to-react-ui/general.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/react-comments-to-react-ui/general.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/react-comments-to-react-ui/general.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/react-comments-to-react-ui/general.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/react-comments-to-react-ui/general.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/imports-suspense.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/imports-suspense.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/imports-suspense.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/imports-suspense.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/imports-suspense.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/imports-suspense.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/imports-suspense.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/imports-suspense.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/imports.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/imports.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/imports.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/imports.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/imports.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/imports.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/imports.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/imports.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/liveblocks-no-types.config.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/liveblocks-no-types.config.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/liveblocks-no-types.config.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/liveblocks-no-types.config.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/liveblocks-no-types.config.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/liveblocks-no-types.config.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/liveblocks-no-types.config.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/liveblocks-no-types.config.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/liveblocks.config.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/liveblocks.config.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/liveblocks.config.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/liveblocks.config.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/liveblocks.config.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/liveblocks.config.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-liveblocks-config-contexts/liveblocks.config.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-liveblocks-config-contexts/liveblocks.config.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/general.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/general.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/general.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/general.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/general.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/general.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/general.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/general.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/renamed-local.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/renamed-local.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/renamed-local.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/renamed-local.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/renamed-local.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/renamed-local.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/renamed-local.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/renamed-local.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/typed.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/typed.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/typed.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/typed.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/typed.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/typed.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/typed.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/typed.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/unrelated.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/unrelated.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/unrelated.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/unrelated.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/unrelated.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/unrelated.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-unneeded-type-params/unrelated.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-unneeded-type-params/unrelated.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/general.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/general.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/general.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/general.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/general.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/general.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/general.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/general.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/named.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/named.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/named.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/named.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/named.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/named.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/named.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/named.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/unrelated.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/unrelated.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/unrelated.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/unrelated.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/unrelated.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/unrelated.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/remove-yjs-default-export/unrelated.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/remove-yjs-default-export/unrelated.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/client-unrelated.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/client-unrelated.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/client-unrelated.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/client-unrelated.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/client-unrelated.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/client-unrelated.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/client-unrelated.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/client-unrelated.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/client.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/client.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/client.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/client.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/client.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/client.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/client.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/client.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/node-unrelated.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/node-unrelated.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/node-unrelated.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/node-unrelated.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/node-unrelated.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/node-unrelated.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/node-unrelated.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/node-unrelated.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/node.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/node.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/node.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/node.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/node.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/node.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/node.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/node.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-alt-liveblocks.config.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-alt-liveblocks.config.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-alt-liveblocks.config.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-alt-liveblocks.config.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-alt-liveblocks.config.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-alt-liveblocks.config.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-alt-liveblocks.config.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-alt-liveblocks.config.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-liveblocks.config.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-liveblocks.config.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-liveblocks.config.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-liveblocks.config.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-liveblocks.config.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-liveblocks.config.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-liveblocks.config.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-liveblocks.config.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-unrelated.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-unrelated.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-unrelated.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-unrelated.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-unrelated.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-unrelated.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react-unrelated.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react-unrelated.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/rename-notification-settings/react.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/rename-notification-settings/react.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/general.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/general.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/general.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/general.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/general.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/general.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/general.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/general.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/import-type.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/import-type.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/import-type.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/import-type.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/import-type.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/import-type.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/import-type.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/import-type.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/type.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/type.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/type.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/type.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/type.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/type.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/type.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/type.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/unrelated.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/unrelated.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/unrelated.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/unrelated.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/unrelated.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/unrelated.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/room-info-to-room-data/unrelated.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/room-info-to-room-data/unrelated.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/simplify-client-side-suspense-children/comments.input.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/simplify-client-side-suspense-children/comments.input.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/simplify-client-side-suspense-children/comments.input.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/simplify-client-side-suspense-children/comments.input.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__testfixtures__/simplify-client-side-suspense-children/comments.output.tsx b/tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/simplify-client-side-suspense-children/comments.output.tsx similarity index 100% rename from tools/liveblocks-codemod/src/transforms/__testfixtures__/simplify-client-side-suspense-children/comments.output.tsx rename to tools/liveblocks-codemod/src/transforms/__tests__/__fixtures__/simplify-client-side-suspense-children/comments.output.tsx diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/_utils.ts b/tools/liveblocks-codemod/src/transforms/__tests__/_utils.ts new file mode 100644 index 00000000000..41b2a4ccf13 --- /dev/null +++ b/tools/liveblocks-codemod/src/transforms/__tests__/_utils.ts @@ -0,0 +1,65 @@ +import path from "path"; +import { readFileSync, readdirSync } from "fs"; +import { describe, test, expect } from "vitest"; +import jscodeshift from "jscodeshift"; + +const INPUT_FILE_REGEX = /\.input\.([jt]sx?)$/; + +// A Vitest-compatible simplified reimplementation of the `defineTest` helper from jscodeshift. +// https://github.com/facebook/jscodeshift/blob/main/src/testUtils.js +export function defineTestsForTransform( + transform: string, + options?: + | Record + | ((args: { transform: string; fixture: string }) => Record) +) { + const transformPath = path.resolve(__dirname, "..", transform); + const fixturesPath = path.resolve(__dirname, "./__fixtures__", transform); + const fixtures = readdirSync(fixturesPath) + .map((file) => { + const match = file.match(INPUT_FILE_REGEX); + + if (!match) { + return null; + } + + const [, extension] = match; + const fixture = file.replace(INPUT_FILE_REGEX, ""); + + return { + name: fixture, + inputPath: path.join(fixturesPath, `${fixture}.input.${extension}`), + outputPath: path.join(fixturesPath, `${fixture}.output.${extension}`), + }; + }) + .filter((fixture) => fixture !== null); + + describe(transform, () => { + for (const fixture of fixtures) { + test(`should transform ${fixture.name}`, async () => { + const input = readFileSync(fixture.inputPath, "utf8"); + const output = readFileSync(fixture.outputPath, "utf8"); + + const transform = await import(transformPath); + const result = transform.default( + { + path: fixture.inputPath, + source: input, + }, + { + jscodeshift: jscodeshift.withParser("tsx"), + stats: () => {}, + }, + { + parser: "tsx", + ...(typeof options === "function" + ? options({ transform, fixture: fixture.name }) + : options), + } + ); + + expect(result).toBe(output); + }); + } + }); +} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/live-list-constructor.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/live-list-constructor.test.ts deleted file mode 100644 index 88eac673710..00000000000 --- a/tools/liveblocks-codemod/src/transforms/__tests__/live-list-constructor.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable */ - -// Based on https://github.com/vercel/next.js/blob/main/packages/next-codemod -{ - const defineTest = require("jscodeshift/dist/testUtils").defineTest; - const { readdirSync } = require("fs"); - const { join } = require("path"); - - const fixtureDir = "live-list-constructor"; - const fixtureDirPath = join(__dirname, "..", "__testfixtures__", fixtureDir); - const fixtures = readdirSync(fixtureDirPath) - .filter((file) => file.endsWith(".input.tsx")) - .map((file) => file.replace(".input.tsx", "")); - - for (const fixture of fixtures) { - const prefix = `${fixtureDir}/${fixture}`; - defineTest(__dirname, fixtureDir, null, prefix, { parser: "tsx" }); - } -} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/liveblocks-ui-config.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/liveblocks-ui-config.test.ts deleted file mode 100644 index c8cc8abdf8f..00000000000 --- a/tools/liveblocks-codemod/src/transforms/__tests__/liveblocks-ui-config.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable */ - -// Based on https://github.com/vercel/next.js/blob/main/packages/next-codemod -{ - const defineTest = require("jscodeshift/dist/testUtils").defineTest; - const { readdirSync } = require("fs"); - const { join } = require("path"); - - const fixtureDir = "liveblocks-ui-config"; - const fixtureDirPath = join(__dirname, "..", "__testfixtures__", fixtureDir); - const fixtures = readdirSync(fixtureDirPath) - .filter((file) => file.endsWith(".input.tsx")) - .map((file) => file.replace(".input.tsx", "")); - - for (const fixture of fixtures) { - const prefix = `${fixtureDir}/${fixture}`; - defineTest(__dirname, fixtureDir, null, prefix, { parser: "tsx" }); - } -} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/react-comments-to-react-ui.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/react-comments-to-react-ui.test.ts deleted file mode 100644 index b6abe2cb903..00000000000 --- a/tools/liveblocks-codemod/src/transforms/__tests__/react-comments-to-react-ui.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable */ - -// Based on https://github.com/vercel/next.js/blob/main/packages/next-codemod -{ - const defineTest = require("jscodeshift/dist/testUtils").defineTest; - const { readdirSync } = require("fs"); - const { join } = require("path"); - - const fixtureDir = "react-comments-to-react-ui"; - const fixtureDirPath = join(__dirname, "..", "__testfixtures__", fixtureDir); - const fixtures = readdirSync(fixtureDirPath) - .filter((file) => file.endsWith(".input.tsx")) - .map((file) => file.replace(".input.tsx", "")); - - for (const fixture of fixtures) { - const prefix = `${fixtureDir}/${fixture}`; - defineTest(__dirname, fixtureDir, null, prefix, { parser: "tsx" }); - } -} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/remove-liveblocks-config-contexts.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/remove-liveblocks-config-contexts.test.ts deleted file mode 100644 index ddd76ff659f..00000000000 --- a/tools/liveblocks-codemod/src/transforms/__tests__/remove-liveblocks-config-contexts.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable */ - -// Based on https://github.com/vercel/next.js/blob/main/packages/next-codemod -{ - const defineTest = require("jscodeshift/dist/testUtils").defineTest; - const { readdirSync } = require("fs"); - const { join } = require("path"); - - const fixtureDir = "remove-liveblocks-config-contexts"; - const fixtureDirPath = join(__dirname, "..", "__testfixtures__", fixtureDir); - const fixtures = readdirSync(fixtureDirPath) - .filter((file) => file.endsWith(".input.tsx")) - .map((file) => file.replace(".input.tsx", "")); - - for (const fixture of fixtures) { - const prefix = `${fixtureDir}/${fixture}`; - const options = {} as { suspense?: boolean }; - - if (fixture.includes("suspense")) { - options.suspense = true; - } - - defineTest(__dirname, fixtureDir, options, prefix, { - parser: "tsx", - }); - } -} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/remove-unneeded-type-params.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/remove-unneeded-type-params.test.ts deleted file mode 100644 index 778a9b87610..00000000000 --- a/tools/liveblocks-codemod/src/transforms/__tests__/remove-unneeded-type-params.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable */ - -// Based on https://github.com/vercel/next.js/blob/main/packages/next-codemod -{ - const defineTest = require("jscodeshift/dist/testUtils").defineTest; - const { readdirSync } = require("fs"); - const { join } = require("path"); - - const fixtureDir = "remove-unneeded-type-params"; - const fixtureDirPath = join(__dirname, "..", "__testfixtures__", fixtureDir); - const fixtures = readdirSync(fixtureDirPath) - .filter((file) => file.endsWith(".input.tsx")) - .map((file) => file.replace(".input.tsx", "")); - - for (const fixture of fixtures) { - const prefix = `${fixtureDir}/${fixture}`; - defineTest(__dirname, fixtureDir, null, prefix, { parser: "tsx" }); - } -} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/remove-yjs-default-export.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/remove-yjs-default-export.test.ts deleted file mode 100644 index 85b6232b601..00000000000 --- a/tools/liveblocks-codemod/src/transforms/__tests__/remove-yjs-default-export.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable */ - -// Based on https://github.com/vercel/next.js/blob/main/packages/next-codemod -{ - const defineTest = require("jscodeshift/dist/testUtils").defineTest; - const { readdirSync } = require("fs"); - const { join } = require("path"); - - const fixtureDir = "remove-yjs-default-export"; - const fixtureDirPath = join(__dirname, "..", "__testfixtures__", fixtureDir); - const fixtures = readdirSync(fixtureDirPath) - .filter((file) => file.endsWith(".input.tsx")) - .map((file) => file.replace(".input.tsx", "")); - - for (const fixture of fixtures) { - const prefix = `${fixtureDir}/${fixture}`; - defineTest(__dirname, fixtureDir, null, prefix, { parser: "tsx" }); - } -} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/rename-notification-settings.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/rename-notification-settings.test.ts deleted file mode 100644 index 1935531c506..00000000000 --- a/tools/liveblocks-codemod/src/transforms/__tests__/rename-notification-settings.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable */ - -// Based on https://github.com/vercel/next.js/blob/main/packages/next-codemod -{ - const defineTest = require("jscodeshift/dist/testUtils").defineTest; - const { readdirSync } = require("fs"); - const { join } = require("path"); - - const fixtureDir = "rename-notification-settings"; - const fixtureDirPath = join(__dirname, "..", "__testfixtures__", fixtureDir); - const fixtures = readdirSync(fixtureDirPath) - .filter((file) => file.endsWith(".input.tsx")) - .map((file) => file.replace(".input.tsx", "")); - - for (const fixture of fixtures) { - const prefix = `${fixtureDir}/${fixture}`; - defineTest(__dirname, fixtureDir, null, prefix, { parser: "tsx" }); - } -} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/room-info-to-room-data.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/room-info-to-room-data.test.ts deleted file mode 100644 index f40aef86c2c..00000000000 --- a/tools/liveblocks-codemod/src/transforms/__tests__/room-info-to-room-data.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable */ - -// Based on https://github.com/vercel/next.js/blob/main/packages/next-codemod -{ - const defineTest = require("jscodeshift/dist/testUtils").defineTest; - const { readdirSync } = require("fs"); - const { join } = require("path"); - - const fixtureDir = "room-info-to-room-data"; - const fixtureDirPath = join(__dirname, "..", "__testfixtures__", fixtureDir); - const fixtures = readdirSync(fixtureDirPath) - .filter((file) => file.endsWith(".input.tsx")) - .map((file) => file.replace(".input.tsx", "")); - - for (const fixture of fixtures) { - const prefix = `${fixtureDir}/${fixture}`; - defineTest(__dirname, fixtureDir, null, prefix, { parser: "tsx" }); - } -} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/simplify-client-side-suspense-children.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/simplify-client-side-suspense-children.test.ts deleted file mode 100644 index 2902340aa0b..00000000000 --- a/tools/liveblocks-codemod/src/transforms/__tests__/simplify-client-side-suspense-children.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable */ - -// Based on https://github.com/vercel/next.js/blob/main/packages/next-codemod -{ - const defineTest = require("jscodeshift/dist/testUtils").defineTest; - const { readdirSync } = require("fs"); - const { join } = require("path"); - - const fixtureDir = "simplify-client-side-suspense-children"; - const fixtureDirPath = join(__dirname, "..", "__testfixtures__", fixtureDir); - const fixtures = readdirSync(fixtureDirPath) - .filter((file) => file.endsWith(".input.tsx")) - .map((file) => file.replace(".input.tsx", "")); - - for (const fixture of fixtures) { - const prefix = `${fixtureDir}/${fixture}`; - const options = {} as { suspense?: boolean }; - - if (fixture.includes("suspense")) { - options.suspense = true; - } - - defineTest(__dirname, fixtureDir, options, prefix, { - parser: "tsx", - }); - } -} diff --git a/tools/liveblocks-codemod/src/transforms/__tests__/transforms.test.ts b/tools/liveblocks-codemod/src/transforms/__tests__/transforms.test.ts new file mode 100644 index 00000000000..0f7b74d259b --- /dev/null +++ b/tools/liveblocks-codemod/src/transforms/__tests__/transforms.test.ts @@ -0,0 +1,19 @@ +import { describe } from "vitest"; +import { defineTestsForTransform } from "./_utils"; + +describe("@liveblocks/codemod", () => { + defineTestsForTransform("live-list-constructor"); + defineTestsForTransform("liveblocks-ui-config"); + defineTestsForTransform("react-comments-to-react-ui"); + defineTestsForTransform( + "remove-liveblocks-config-contexts", + ({ fixture }) => ({ + suspense: fixture.includes("suspense"), + }) + ); + defineTestsForTransform("remove-unneeded-type-params"); + defineTestsForTransform("remove-yjs-default-export"); + defineTestsForTransform("rename-notification-settings"); + defineTestsForTransform("room-info-to-room-data"); + defineTestsForTransform("simplify-client-side-suspense-children"); +}); diff --git a/tools/liveblocks-codemod/tsconfig.json b/tools/liveblocks-codemod/tsconfig.json index 6c6846904d7..678c37e39fd 100644 --- a/tools/liveblocks-codemod/tsconfig.json +++ b/tools/liveblocks-codemod/tsconfig.json @@ -13,6 +13,7 @@ "dist", "node_modules", "src/transforms/__tests__", - "src/transforms/__testfixtures__" + "src/replacements/__tests__", + "vitest.config.ts" ] } diff --git a/tools/liveblocks-codemod/vitest.config.ts b/tools/liveblocks-codemod/vitest.config.ts new file mode 100644 index 00000000000..17e36553267 --- /dev/null +++ b/tools/liveblocks-codemod/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vitest/config"; + +// `@liveblocks/codemod` cannot move to ESM so we can't use +// `defaultLiveblocksVitestConfig` from `@liveblocks/vitest-config`. +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts"], + coverage: { + provider: "istanbul", + reporter: ["text", "html"], + }, + }, +}); From 8267302aab29aa6be1c0ca1a0df72a1ad201fce6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Wed, 15 Apr 2026 10:20:38 +0000 Subject: [PATCH 3/7] Bump to 3.18.2 --- package-lock.json | 114 +++++++++--------- .../liveblocks-chat-sdk-adapter/package.json | 6 +- packages/liveblocks-client/package.json | 4 +- packages/liveblocks-core/package.json | 2 +- packages/liveblocks-emails/package.json | 6 +- packages/liveblocks-node-lexical/package.json | 6 +- .../liveblocks-node-prosemirror/package.json | 6 +- packages/liveblocks-node/package.json | 4 +- .../liveblocks-react-blocknote/package.json | 14 +-- packages/liveblocks-react-flow/package.json | 10 +- .../liveblocks-react-lexical/package.json | 12 +- packages/liveblocks-react-tiptap/package.json | 12 +- packages/liveblocks-react-ui/package.json | 8 +- packages/liveblocks-react/package.json | 6 +- packages/liveblocks-redux/package.json | 6 +- packages/liveblocks-yjs/package.json | 6 +- packages/liveblocks-zustand/package.json | 6 +- 17 files changed, 114 insertions(+), 114 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ab8fe5c8c3..c8ae3d35f69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37488,11 +37488,11 @@ }, "packages/liveblocks-chat-sdk-adapter": { "name": "@liveblocks/chat-sdk-adapter", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.18.1", - "@liveblocks/node": "3.18.1" + "@liveblocks/core": "3.18.2", + "@liveblocks/node": "3.18.2" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37505,10 +37505,10 @@ }, "packages/liveblocks-client": { "name": "@liveblocks/client", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.18.1" + "@liveblocks/core": "3.18.2" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37517,7 +37517,7 @@ }, "packages/liveblocks-core": { "name": "@liveblocks/core", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37535,11 +37535,11 @@ }, "packages/liveblocks-emails": { "name": "@liveblocks/emails", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.18.1", - "@liveblocks/node": "3.18.1" + "@liveblocks/core": "3.18.2", + "@liveblocks/node": "3.18.2" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37558,10 +37558,10 @@ }, "packages/liveblocks-node": { "name": "@liveblocks/node", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.18.1", + "@liveblocks/core": "3.18.2", "@stablelib/base64": "^1.0.1", "fast-sha256": "^1.3.0", "node-fetch": "^2.6.1" @@ -37576,11 +37576,11 @@ }, "packages/liveblocks-node-lexical": { "name": "@liveblocks/node-lexical", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.18.1", - "@liveblocks/node": "3.18.1", + "@liveblocks/core": "3.18.2", + "@liveblocks/node": "3.18.2", "yjs": "^13.6.18" }, "devDependencies": { @@ -37597,11 +37597,11 @@ }, "packages/liveblocks-node-prosemirror": { "name": "@liveblocks/node-prosemirror", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.18.1", - "@liveblocks/node": "3.18.1", + "@liveblocks/core": "3.18.2", + "@liveblocks/node": "3.18.2", "yjs": "^13.6.20" }, "devDependencies": { @@ -37621,11 +37621,11 @@ }, "packages/liveblocks-react": { "name": "@liveblocks/react", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1" + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37655,15 +37655,15 @@ }, "packages/liveblocks-react-blocknote": { "name": "@liveblocks/react-blocknote", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", - "@liveblocks/react-tiptap": "3.18.1", - "@liveblocks/react-ui": "3.18.1", - "@liveblocks/yjs": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", + "@liveblocks/react-tiptap": "3.18.2", + "@liveblocks/react-ui": "3.18.2", + "@liveblocks/yjs": "3.18.2", "@tiptap/core": "^3.19.0", "vitest-tsconfig-paths": "^3.4.1" }, @@ -37700,13 +37700,13 @@ }, "packages/liveblocks-react-flow": { "name": "@liveblocks/react-flow", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", - "@liveblocks/react-ui": "3.18.1" + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", + "@liveblocks/react-ui": "3.18.2" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37770,15 +37770,15 @@ }, "packages/liveblocks-react-lexical": { "name": "@liveblocks/react-lexical", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { "@floating-ui/react-dom": "^2.1.0", - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", - "@liveblocks/react-ui": "3.18.1", - "@liveblocks/yjs": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", + "@liveblocks/react-ui": "3.18.2", + "@liveblocks/yjs": "3.18.2", "radix-ui": "^1.4.0", "yjs": "^13.6.18" }, @@ -39412,15 +39412,15 @@ }, "packages/liveblocks-react-tiptap": { "name": "@liveblocks/react-tiptap", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { "@floating-ui/react-dom": "^2.1.0", - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", - "@liveblocks/react-ui": "3.18.1", - "@liveblocks/yjs": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", + "@liveblocks/react-ui": "3.18.2", + "@liveblocks/yjs": "3.18.2", "@tiptap/core": "^3.19.0", "@tiptap/react": "^3.19.0", "@tiptap/suggestion": "^3.19.0", @@ -41068,13 +41068,13 @@ }, "packages/liveblocks-react-ui": { "name": "@liveblocks/react-ui", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { "@floating-ui/react-dom": "^2.1.0", - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", "frimousse": "^0.2.0", "marked": "^15.0.11", "radix-ui": "^1.4.0", @@ -41371,11 +41371,11 @@ }, "packages/liveblocks-redux": { "name": "@liveblocks/redux", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1" + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -41531,11 +41531,11 @@ }, "packages/liveblocks-yjs": { "name": "@liveblocks/yjs", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", "@noble/hashes": "^1.8.0", "js-base64": "^3.7.7", "y-indexeddb": "^9.0.12" @@ -41600,11 +41600,11 @@ }, "packages/liveblocks-zustand": { "name": "@liveblocks/zustand", - "version": "3.18.1", + "version": "3.18.2", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1" + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2" }, "devDependencies": { "@liveblocks/eslint-config": "*", diff --git a/packages/liveblocks-chat-sdk-adapter/package.json b/packages/liveblocks-chat-sdk-adapter/package.json index 216f50b9be2..d5c48f48043 100644 --- a/packages/liveblocks-chat-sdk-adapter/package.json +++ b/packages/liveblocks-chat-sdk-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/chat-sdk-adapter", - "version": "3.18.1", + "version": "3.18.2", "description": "Liveblocks adapter for the Chat SDK.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -35,8 +35,8 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/core": "3.18.1", - "@liveblocks/node": "3.18.1" + "@liveblocks/core": "3.18.2", + "@liveblocks/node": "3.18.2" }, "peerDependencies": { "chat": ">=4.20.0" diff --git a/packages/liveblocks-client/package.json b/packages/liveblocks-client/package.json index 7ab0c57c46b..6c05ad3e7cd 100644 --- a/packages/liveblocks-client/package.json +++ b/packages/liveblocks-client/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/client", - "version": "3.18.1", + "version": "3.18.2", "description": "A client that lets you interact with Liveblocks servers. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -36,7 +36,7 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/core": "3.18.1" + "@liveblocks/core": "3.18.2" }, "devDependencies": { "@liveblocks/eslint-config": "*", diff --git a/packages/liveblocks-core/package.json b/packages/liveblocks-core/package.json index d72c0f1a262..c5097b9c6ce 100644 --- a/packages/liveblocks-core/package.json +++ b/packages/liveblocks-core/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/core", - "version": "3.18.1", + "version": "3.18.2", "description": "Private internals for Liveblocks. DO NOT import directly from this package!", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/liveblocks-emails/package.json b/packages/liveblocks-emails/package.json index 43cffecaa1a..f96baa91d5b 100644 --- a/packages/liveblocks-emails/package.json +++ b/packages/liveblocks-emails/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/emails", - "version": "3.18.1", + "version": "3.18.2", "description": "A set of functions and utilities to make sending emails based on Liveblocks notification events easy. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -36,8 +36,8 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/core": "3.18.1", - "@liveblocks/node": "3.18.1" + "@liveblocks/core": "3.18.2", + "@liveblocks/node": "3.18.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc" diff --git a/packages/liveblocks-node-lexical/package.json b/packages/liveblocks-node-lexical/package.json index 8665b1e04a4..44a42811c76 100644 --- a/packages/liveblocks-node-lexical/package.json +++ b/packages/liveblocks-node-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/node-lexical", - "version": "3.18.1", + "version": "3.18.2", "description": "A server-side utility that lets you modify lexical documents hosted in Liveblocks.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -35,8 +35,8 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/core": "3.18.1", - "@liveblocks/node": "3.18.1", + "@liveblocks/core": "3.18.2", + "@liveblocks/node": "3.18.2", "yjs": "^13.6.18" }, "peerDependencies": { diff --git a/packages/liveblocks-node-prosemirror/package.json b/packages/liveblocks-node-prosemirror/package.json index 8ab909bb9b8..61cf58a77d7 100644 --- a/packages/liveblocks-node-prosemirror/package.json +++ b/packages/liveblocks-node-prosemirror/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/node-prosemirror", - "version": "3.18.1", + "version": "3.18.2", "description": "A server-side utility that lets you modify prosemirror and tiptap documents hosted in Liveblocks.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -35,8 +35,8 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/core": "3.18.1", - "@liveblocks/node": "3.18.1", + "@liveblocks/core": "3.18.2", + "@liveblocks/node": "3.18.2", "yjs": "^13.6.20" }, "peerDependencies": { diff --git a/packages/liveblocks-node/package.json b/packages/liveblocks-node/package.json index 1d568ace6be..0d41a9afc49 100644 --- a/packages/liveblocks-node/package.json +++ b/packages/liveblocks-node/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/node", - "version": "3.18.1", + "version": "3.18.2", "description": "A server-side utility that lets you set up a Liveblocks authentication endpoint. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -36,7 +36,7 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/core": "3.18.1", + "@liveblocks/core": "3.18.2", "@stablelib/base64": "^1.0.1", "fast-sha256": "^1.3.0", "node-fetch": "^2.6.1" diff --git a/packages/liveblocks-react-blocknote/package.json b/packages/liveblocks-react-blocknote/package.json index b974df59024..8451023db02 100644 --- a/packages/liveblocks-react-blocknote/package.json +++ b/packages/liveblocks-react-blocknote/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-blocknote", - "version": "3.18.1", + "version": "3.18.2", "description": "An integration of BlockNote + React to enable collaboration, comments, live cursors, and more with Liveblocks.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -44,12 +44,12 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", - "@liveblocks/react-tiptap": "3.18.1", - "@liveblocks/react-ui": "3.18.1", - "@liveblocks/yjs": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", + "@liveblocks/react-tiptap": "3.18.2", + "@liveblocks/react-ui": "3.18.2", + "@liveblocks/yjs": "3.18.2", "@tiptap/core": "^3.19.0", "vitest-tsconfig-paths": "^3.4.1" }, diff --git a/packages/liveblocks-react-flow/package.json b/packages/liveblocks-react-flow/package.json index 4d041f74a36..984108044ca 100644 --- a/packages/liveblocks-react-flow/package.json +++ b/packages/liveblocks-react-flow/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-flow", - "version": "3.18.1", + "version": "3.18.2", "description": "An integration of React Flow to enable collaboration and realtime cursors with Liveblocks.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -63,10 +63,10 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", - "@liveblocks/react-ui": "3.18.1" + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", + "@liveblocks/react-ui": "3.18.2" }, "peerDependencies": { "@xyflow/react": "^12", diff --git a/packages/liveblocks-react-lexical/package.json b/packages/liveblocks-react-lexical/package.json index e483d5bc28d..2c60bb9947a 100644 --- a/packages/liveblocks-react-lexical/package.json +++ b/packages/liveblocks-react-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-lexical", - "version": "3.18.1", + "version": "3.18.2", "description": "An integration of Lexical + React to enable collaboration, comments, live cursors, and more with Liveblocks.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -45,11 +45,11 @@ }, "dependencies": { "@floating-ui/react-dom": "^2.1.0", - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", - "@liveblocks/react-ui": "3.18.1", - "@liveblocks/yjs": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", + "@liveblocks/react-ui": "3.18.2", + "@liveblocks/yjs": "3.18.2", "radix-ui": "^1.4.0", "yjs": "^13.6.18" }, diff --git a/packages/liveblocks-react-tiptap/package.json b/packages/liveblocks-react-tiptap/package.json index a50ece7e468..fe30fabc8e1 100644 --- a/packages/liveblocks-react-tiptap/package.json +++ b/packages/liveblocks-react-tiptap/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-tiptap", - "version": "3.18.1", + "version": "3.18.2", "description": "An integration of TipTap + React to enable collaboration, comments, live cursors, and more with Liveblocks.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -45,11 +45,11 @@ }, "dependencies": { "@floating-ui/react-dom": "^2.1.0", - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", - "@liveblocks/react-ui": "3.18.1", - "@liveblocks/yjs": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", + "@liveblocks/react-ui": "3.18.2", + "@liveblocks/yjs": "3.18.2", "@tiptap/core": "^3.19.0", "@tiptap/react": "^3.19.0", "@tiptap/suggestion": "^3.19.0", diff --git a/packages/liveblocks-react-ui/package.json b/packages/liveblocks-react-ui/package.json index dd51d0a849e..ea9f36ac7fd 100644 --- a/packages/liveblocks-react-ui/package.json +++ b/packages/liveblocks-react-ui/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-ui", - "version": "3.18.1", + "version": "3.18.2", "description": "A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -78,9 +78,9 @@ }, "dependencies": { "@floating-ui/react-dom": "^2.1.0", - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", - "@liveblocks/react": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", + "@liveblocks/react": "3.18.2", "frimousse": "^0.2.0", "marked": "^15.0.11", "radix-ui": "^1.4.0", diff --git a/packages/liveblocks-react/package.json b/packages/liveblocks-react/package.json index 44fb8c37a97..dabdeddfc3c 100644 --- a/packages/liveblocks-react/package.json +++ b/packages/liveblocks-react/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react", - "version": "3.18.1", + "version": "3.18.2", "description": "A set of React hooks and providers to use Liveblocks declaratively. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -63,8 +63,8 @@ "showdeps": "depcruise src --include-only '^src' --exclude='__tests__' --output-type dot | dot -T svg > /tmp/dependency-graph.svg && open /tmp/dependency-graph.svg" }, "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1" + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2" }, "peerDependencies": { "@types/react": "*", diff --git a/packages/liveblocks-redux/package.json b/packages/liveblocks-redux/package.json index ab6cfee4285..52d085bc288 100644 --- a/packages/liveblocks-redux/package.json +++ b/packages/liveblocks-redux/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/redux", - "version": "3.18.1", + "version": "3.18.2", "description": "A store enhancer to integrate Liveblocks into Redux stores. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -35,8 +35,8 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1" + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2" }, "peerDependencies": { "redux": "^4 || ^5" diff --git a/packages/liveblocks-yjs/package.json b/packages/liveblocks-yjs/package.json index 4a524e97d2c..1587fa3dcfc 100644 --- a/packages/liveblocks-yjs/package.json +++ b/packages/liveblocks-yjs/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/yjs", - "version": "3.18.1", + "version": "3.18.2", "description": "Integrate your existing or new Yjs documents with Liveblocks.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -35,8 +35,8 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1", + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2", "@noble/hashes": "^1.8.0", "js-base64": "^3.7.7", "y-indexeddb": "^9.0.12" diff --git a/packages/liveblocks-zustand/package.json b/packages/liveblocks-zustand/package.json index bbf58d05447..1227bf54066 100644 --- a/packages/liveblocks-zustand/package.json +++ b/packages/liveblocks-zustand/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/zustand", - "version": "3.18.1", + "version": "3.18.2", "description": "A middleware for Zustand to automatically synchronize your stores with Liveblocks. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "author": "Liveblocks Inc.", @@ -36,8 +36,8 @@ "test:watch": "vitest" }, "dependencies": { - "@liveblocks/client": "3.18.1", - "@liveblocks/core": "3.18.1" + "@liveblocks/client": "3.18.2", + "@liveblocks/core": "3.18.2" }, "peerDependencies": { "zustand": "^5.0.1" From 1e97560f06c5d20e21567506df600a67a238c05a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:31:41 +0100 Subject: [PATCH 4/7] Bump follow-redirects from 1.15.11 to 1.16.0 in /examples/nextjs-comments-emails-sendgrid (#3355) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/nextjs-comments-emails-sendgrid/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/nextjs-comments-emails-sendgrid/package-lock.json b/examples/nextjs-comments-emails-sendgrid/package-lock.json index 4258bee9831..b76fa28c181 100644 --- a/examples/nextjs-comments-emails-sendgrid/package-lock.json +++ b/examples/nextjs-comments-emails-sendgrid/package-lock.json @@ -2678,9 +2678,9 @@ "license": "Unlicense" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", From 0f31a7df6664d5143f4699e88d6933ff5b90687f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:31:46 +0100 Subject: [PATCH 5/7] Bump next from 16.2.0 to 16.2.3 in /examples/nextjs-comments-audio (#3356) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../nextjs-comments-audio/package-lock.json | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/examples/nextjs-comments-audio/package-lock.json b/examples/nextjs-comments-audio/package-lock.json index 577b6072c7b..23e4873d855 100644 --- a/examples/nextjs-comments-audio/package-lock.json +++ b/examples/nextjs-comments-audio/package-lock.json @@ -702,15 +702,15 @@ } }, "node_modules/@next/env": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.0.tgz", - "integrity": "sha512-OZIbODWWAi0epQRCRjNe1VO45LOFBzgiyqmTLzIqWq6u1wrxKnAyz1HH6tgY/Mc81YzIjRPoYsPAEr4QV4l9TA==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.3.tgz", + "integrity": "sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.0.tgz", - "integrity": "sha512-/JZsqKzKt01IFoiLLAzlNqys7qk2F3JkcUhj50zuRhKDQkZNOz9E5N6wAQWprXdsvjRP4lTFj+/+36NSv5AwhQ==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.3.tgz", + "integrity": "sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==", "cpu": [ "arm64" ], @@ -724,9 +724,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.0.tgz", - "integrity": "sha512-/hV8erWq4SNlVgglUiW5UmQ5Hwy5EW/AbbXlJCn6zkfKxTy/E/U3V8U1Ocm2YCTUoFgQdoMxRyRMOW5jYy4ygg==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.3.tgz", + "integrity": "sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==", "cpu": [ "x64" ], @@ -740,9 +740,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.0.tgz", - "integrity": "sha512-GkjL/Q7MWOwqWR9zoxu1TIHzkOI2l2BHCf7FzeQG87zPgs+6WDh+oC9Sw9ARuuL/FUk6JNCgKRkA6rEQYadUaw==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.3.tgz", + "integrity": "sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==", "cpu": [ "arm64" ], @@ -756,9 +756,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.0.tgz", - "integrity": "sha512-1ffhC6KY5qWLg5miMlKJp3dZbXelEfjuXt1qcp5WzSCQy36CV3y+JT7OC1WSFKizGQCDOcQbfkH/IjZP3cdRNA==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.3.tgz", + "integrity": "sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==", "cpu": [ "arm64" ], @@ -772,9 +772,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.0.tgz", - "integrity": "sha512-FmbDcZQ8yJRq93EJSL6xaE0KK/Rslraf8fj1uViGxg7K4CKBCRYSubILJPEhjSgZurpcPQq12QNOJQ0DRJl6Hg==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.3.tgz", + "integrity": "sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==", "cpu": [ "x64" ], @@ -788,9 +788,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.0.tgz", - "integrity": "sha512-HzjIHVkmGAwRbh/vzvoBWWEbb8BBZPxBvVbDQDvzHSf3D8RP/4vjw7MNLDXFF9Q1WEzeQyEj2zdxBtVAHu5Oyw==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.3.tgz", + "integrity": "sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==", "cpu": [ "x64" ], @@ -804,9 +804,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.0.tgz", - "integrity": "sha512-UMiFNQf5H7+1ZsZPxEsA064WEuFbRNq/kEXyepbCnSErp4f5iut75dBA8UeerFIG3vDaQNOfCpevnERPp2V+nA==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.3.tgz", + "integrity": "sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==", "cpu": [ "arm64" ], @@ -820,9 +820,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.0.tgz", - "integrity": "sha512-DRrNJKW+/eimrZgdhVN1uvkN1OI4j6Lpefwr44jKQ0YQzztlmOBUUzHuV5GxOMPK3nmodAYElUVCY8ZXo/IWeA==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.3.tgz", + "integrity": "sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==", "cpu": [ "x64" ], @@ -3107,12 +3107,12 @@ } }, "node_modules/next": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.0.tgz", - "integrity": "sha512-NLBVrJy1pbV1Yn00L5sU4vFyAHt5XuSjzrNyFnxo6Com0M0KrL6hHM5B99dbqXb2bE9pm4Ow3Zl1xp6HVY9edQ==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.3.tgz", + "integrity": "sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==", "license": "MIT", "dependencies": { - "@next/env": "16.2.0", + "@next/env": "16.2.3", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -3126,14 +3126,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.0", - "@next/swc-darwin-x64": "16.2.0", - "@next/swc-linux-arm64-gnu": "16.2.0", - "@next/swc-linux-arm64-musl": "16.2.0", - "@next/swc-linux-x64-gnu": "16.2.0", - "@next/swc-linux-x64-musl": "16.2.0", - "@next/swc-win32-arm64-msvc": "16.2.0", - "@next/swc-win32-x64-msvc": "16.2.0", + "@next/swc-darwin-arm64": "16.2.3", + "@next/swc-darwin-x64": "16.2.3", + "@next/swc-linux-arm64-gnu": "16.2.3", + "@next/swc-linux-arm64-musl": "16.2.3", + "@next/swc-linux-x64-gnu": "16.2.3", + "@next/swc-linux-x64-musl": "16.2.3", + "@next/swc-win32-arm64-msvc": "16.2.3", + "@next/swc-win32-x64-msvc": "16.2.3", "sharp": "^0.34.5" }, "peerDependencies": { From 2619c157be378e43ee75194cfdee3aba23c736c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:34:28 +0100 Subject: [PATCH 6/7] Bump next from 16.2.0 to 16.2.3 in /examples/nextjs-comments-search (#3357) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../nextjs-comments-search/package-lock.json | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/examples/nextjs-comments-search/package-lock.json b/examples/nextjs-comments-search/package-lock.json index e15ad114578..36338413942 100644 --- a/examples/nextjs-comments-search/package-lock.json +++ b/examples/nextjs-comments-search/package-lock.json @@ -1946,15 +1946,15 @@ } }, "node_modules/@next/env": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.0.tgz", - "integrity": "sha512-OZIbODWWAi0epQRCRjNe1VO45LOFBzgiyqmTLzIqWq6u1wrxKnAyz1HH6tgY/Mc81YzIjRPoYsPAEr4QV4l9TA==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.3.tgz", + "integrity": "sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.0.tgz", - "integrity": "sha512-/JZsqKzKt01IFoiLLAzlNqys7qk2F3JkcUhj50zuRhKDQkZNOz9E5N6wAQWprXdsvjRP4lTFj+/+36NSv5AwhQ==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.3.tgz", + "integrity": "sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==", "cpu": [ "arm64" ], @@ -1968,9 +1968,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.0.tgz", - "integrity": "sha512-/hV8erWq4SNlVgglUiW5UmQ5Hwy5EW/AbbXlJCn6zkfKxTy/E/U3V8U1Ocm2YCTUoFgQdoMxRyRMOW5jYy4ygg==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.3.tgz", + "integrity": "sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==", "cpu": [ "x64" ], @@ -1984,9 +1984,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.0.tgz", - "integrity": "sha512-GkjL/Q7MWOwqWR9zoxu1TIHzkOI2l2BHCf7FzeQG87zPgs+6WDh+oC9Sw9ARuuL/FUk6JNCgKRkA6rEQYadUaw==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.3.tgz", + "integrity": "sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==", "cpu": [ "arm64" ], @@ -2000,9 +2000,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.0.tgz", - "integrity": "sha512-1ffhC6KY5qWLg5miMlKJp3dZbXelEfjuXt1qcp5WzSCQy36CV3y+JT7OC1WSFKizGQCDOcQbfkH/IjZP3cdRNA==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.3.tgz", + "integrity": "sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==", "cpu": [ "arm64" ], @@ -2016,9 +2016,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.0.tgz", - "integrity": "sha512-FmbDcZQ8yJRq93EJSL6xaE0KK/Rslraf8fj1uViGxg7K4CKBCRYSubILJPEhjSgZurpcPQq12QNOJQ0DRJl6Hg==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.3.tgz", + "integrity": "sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==", "cpu": [ "x64" ], @@ -2032,9 +2032,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.0.tgz", - "integrity": "sha512-HzjIHVkmGAwRbh/vzvoBWWEbb8BBZPxBvVbDQDvzHSf3D8RP/4vjw7MNLDXFF9Q1WEzeQyEj2zdxBtVAHu5Oyw==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.3.tgz", + "integrity": "sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==", "cpu": [ "x64" ], @@ -2048,9 +2048,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.0.tgz", - "integrity": "sha512-UMiFNQf5H7+1ZsZPxEsA064WEuFbRNq/kEXyepbCnSErp4f5iut75dBA8UeerFIG3vDaQNOfCpevnERPp2V+nA==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.3.tgz", + "integrity": "sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==", "cpu": [ "arm64" ], @@ -2064,9 +2064,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.0.tgz", - "integrity": "sha512-DRrNJKW+/eimrZgdhVN1uvkN1OI4j6Lpefwr44jKQ0YQzztlmOBUUzHuV5GxOMPK3nmodAYElUVCY8ZXo/IWeA==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.3.tgz", + "integrity": "sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==", "cpu": [ "x64" ], @@ -2597,12 +2597,12 @@ } }, "node_modules/next": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.0.tgz", - "integrity": "sha512-NLBVrJy1pbV1Yn00L5sU4vFyAHt5XuSjzrNyFnxo6Com0M0KrL6hHM5B99dbqXb2bE9pm4Ow3Zl1xp6HVY9edQ==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.3.tgz", + "integrity": "sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==", "license": "MIT", "dependencies": { - "@next/env": "16.2.0", + "@next/env": "16.2.3", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -2616,14 +2616,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.0", - "@next/swc-darwin-x64": "16.2.0", - "@next/swc-linux-arm64-gnu": "16.2.0", - "@next/swc-linux-arm64-musl": "16.2.0", - "@next/swc-linux-x64-gnu": "16.2.0", - "@next/swc-linux-x64-musl": "16.2.0", - "@next/swc-win32-arm64-msvc": "16.2.0", - "@next/swc-win32-x64-msvc": "16.2.0", + "@next/swc-darwin-arm64": "16.2.3", + "@next/swc-darwin-x64": "16.2.3", + "@next/swc-linux-arm64-gnu": "16.2.3", + "@next/swc-linux-arm64-musl": "16.2.3", + "@next/swc-linux-x64-gnu": "16.2.3", + "@next/swc-linux-x64-musl": "16.2.3", + "@next/swc-win32-arm64-msvc": "16.2.3", + "@next/swc-win32-x64-msvc": "16.2.3", "sharp": "^0.34.5" }, "peerDependencies": { From f62c69e9819f8ffe7ee574739b9fd67367647b6e Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Wed, 15 Apr 2026 12:37:02 +0200 Subject: [PATCH 7/7] Update storage engine migration docs to reflect active migration (#3358) --- guides/pages/about-the-new-storage-engine.mdx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/guides/pages/about-the-new-storage-engine.mdx b/guides/pages/about-the-new-storage-engine.mdx index f7fba303c1f..8ed17a46ade 100644 --- a/guides/pages/about-the-new-storage-engine.mdx +++ b/guides/pages/about-the-new-storage-engine.mdx @@ -58,7 +58,8 @@ advantage of the v2 engine: `npx liveblocks@latest upgrade` ### Migrating existing rooms -Existing rooms that were created before this change remain on the v1 engine. -Eventually, we will transparently migrate all existing room data from the v1 to -the v2 engine. This process will be seamless and require no action on your part. -We will announce this ahead of time. +Starting April 15, 2026, we are transparently migrating all existing room data +from the v1 to the v2 engine. This happens in the background, and you can keep +using your rooms like you normally would. We expect the majority of storage rooms +to be migrated by the end of April, though the long tail may take until the end +of May. We will update this document once the migration is complete for everyone.