rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
124 lines (123 loc) • 4.96 kB
JavaScript
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("cloudflare:workers", () => {
class DurableObject {
}
return { DurableObject };
});
import { SyncedStateServer } from "../SyncedStateServer.mjs";
const createStub = (onInvoke) => {
const makeStub = () => {
const fn = Object.assign(async (value) => {
await onInvoke(value);
}, {
dup: () => makeStub(),
});
return fn;
};
return makeStub();
};
describe("SyncedStateServer", () => {
it("notifies subscribers when state changes", async () => {
const coordinator = new SyncedStateServer({}, {});
const received = [];
const stub = createStub((value) => {
received.push(value);
});
coordinator.subscribe("counter", stub);
coordinator.setState(5, "counter");
expect(coordinator.getState("counter")).toBe(5);
expect(received).toEqual([5]);
});
it("removes subscriptions on unsubscribe", () => {
const coordinator = new SyncedStateServer({}, {});
const received = [];
const stub = createStub((value) => {
received.push(value);
});
coordinator.subscribe("counter", stub);
coordinator.unsubscribe("counter", stub);
coordinator.setState(1, "counter");
expect(coordinator.getState("counter")).toBe(1);
expect(received).toEqual([]);
});
it("drops failing subscribers", async () => {
const coordinator = new SyncedStateServer({}, {});
const stub = createStub(async () => {
throw new Error("fail");
});
coordinator.subscribe("counter", stub);
coordinator.setState(3, "counter");
await Promise.resolve();
coordinator.setState(4, "counter");
expect(coordinator.getState("counter")).toBe(4);
});
it("invokes registered onSet handler", () => {
const coordinator = new SyncedStateServer({}, {});
const mockStub = {};
coordinator.setStub(mockStub);
const calls = [];
SyncedStateServer.registerSetStateHandler((key, value, stub) => {
calls.push({ key, value });
expect(stub).toBe(mockStub);
});
coordinator.setState(2, "counter");
expect(calls).toEqual([{ key: "counter", value: 2 }]);
SyncedStateServer.registerSetStateHandler(null);
});
it("invokes registered onGet handler", () => {
const coordinator = new SyncedStateServer({}, {});
const mockStub = {};
coordinator.setStub(mockStub);
const calls = [];
SyncedStateServer.registerGetStateHandler((key, value, stub) => {
calls.push({ key, value });
expect(stub).toBe(mockStub);
});
coordinator.setState(4, "counter");
expect(coordinator.getState("counter")).toBe(4);
expect(calls).toEqual([{ key: "counter", value: 4 }]);
SyncedStateServer.registerGetStateHandler(null);
});
describe("registerKeyHandler", () => {
const mockStub = {};
afterEach(() => {
SyncedStateServer.registerKeyHandler(async (key, stub) => key);
});
it("stores and retrieves the registered handler", async () => {
const handler = async (key, stub) => `transformed:${key}`;
SyncedStateServer.registerKeyHandler(handler);
const retrievedHandler = SyncedStateServer.getKeyHandler();
expect(retrievedHandler).toBe(handler);
});
it("transforms keys using the registered handler", async () => {
const handler = async (key, stub) => `user:123:${key}`;
SyncedStateServer.registerKeyHandler(handler);
const result = await handler("counter", mockStub);
expect(result).toBe("user:123:counter");
});
it("returns null when no handler is registered", () => {
SyncedStateServer.registerKeyHandler(async (key, stub) => key);
const handler = SyncedStateServer.getKeyHandler();
expect(handler).not.toBeNull();
});
it("allows handler to be async", async () => {
const handler = async (key, stub) => {
await new Promise((resolve) => setTimeout(resolve, 10));
return `async:${key}`;
};
SyncedStateServer.registerKeyHandler(handler);
const result = await handler("test", mockStub);
expect(result).toBe("async:test");
});
it("handler receives the correct key parameter", async () => {
let receivedKey = "";
const handler = async (key, stub) => {
receivedKey = key;
return key;
};
SyncedStateServer.registerKeyHandler(handler);
await handler("myKey", mockStub);
expect(receivedKey).toBe("myKey");
});
});
});