UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

116 lines (115 loc) 4.05 kB
import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { setSyncedStateClientForTesting, } from "../client"; import { createSyncedStateHook, } from "../useSyncedState"; const createStateHarness = () => { let currentState; const cleanups = []; const useStateImpl = ((initialValue) => { const resolved = typeof initialValue === "function" ? initialValue() : initialValue; currentState = resolved; const setState = (next) => { currentState = typeof next === "function" ? next(currentState) : next; }; return [currentState, setState]; }); const useEffectImpl = (callback) => { const cleanup = callback(); if (typeof cleanup === "function") { cleanups.push(cleanup); } }; const useRefImpl = ((value) => ({ current: value, })); const useCallbackImpl = (fn) => fn; const deps = { useState: useStateImpl, useEffect: useEffectImpl, useRef: useRefImpl, useCallback: useCallbackImpl, }; return { deps, getState: () => currentState, runCleanups: () => cleanups.forEach((fn) => fn()), }; }; describe("createSyncedStateHook", () => { const subscribeHandlers = new Map(); const client = { async getState() { return 5; }, async setState(_value, _key) { }, async subscribe(key, handler) { subscribeHandlers.set(key, handler); }, async unsubscribe(key) { subscribeHandlers.delete(key); }, }; const resetClient = () => { client.getState = async () => 5; client.setState = async (_value, _key) => { }; client.subscribe = async (key, handler) => { subscribeHandlers.set(key, handler); }; client.unsubscribe = async (key) => { subscribeHandlers.delete(key); }; }; beforeEach(() => { resetClient(); setSyncedStateClientForTesting(client); subscribeHandlers.clear(); }); afterEach(() => { setSyncedStateClientForTesting(null); }); it("loads remote state and updates local value", async () => { const harness = createStateHarness(); const useSyncedState = createSyncedStateHook({ hooks: harness.deps }); const [value] = useSyncedState(0, "counter"); expect(value).toBe(0); await Promise.resolve(); expect(harness.getState()).toBe(5); }); it("sends updates through the client and applies optimistic value", async () => { const harness = createStateHarness(); const setCalls = []; client.setState = async (value, key) => { setCalls.push({ key, value }); }; const useSyncedState = createSyncedStateHook({ hooks: harness.deps }); const [, setSyncValue] = useSyncedState(0, "counter"); setSyncValue(9); expect(harness.getState()).toBe(9); expect(setCalls).toEqual([{ key: "counter", value: 9 }]); }); it("applies remote updates from the subscription handler", async () => { const harness = createStateHarness(); const useSyncedState = createSyncedStateHook({ hooks: harness.deps }); useSyncedState(0, "counter"); await Promise.resolve(); const handler = subscribeHandlers.get("counter"); handler?.(7); expect(harness.getState()).toBe(7); }); it("unsubscribes during cleanup", () => { const harness = createStateHarness(); const unsubscribed = []; client.unsubscribe = async (key) => { unsubscribed.push({ key }); subscribeHandlers.delete(key); }; const useSyncedState = createSyncedStateHook({ hooks: harness.deps }); useSyncedState(0, "counter"); harness.runCleanups(); expect(unsubscribed).toEqual([{ key: "counter" }]); }); });