diff --git a/deno.lock b/deno.lock
new file mode 100644
index 0000000..7b2008d
--- /dev/null
+++ b/deno.lock
@@ -0,0 +1,33 @@
+{
+ "version": "5",
+ "specifiers": {
+ "jsr:@std/assert@*": "1.0.19",
+ "jsr:@std/internal@^1.0.12": "1.0.14"
+ },
+ "jsr": {
+ "@std/assert@1.0.19": {
+ "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e",
+ "dependencies": [
+ "jsr:@std/internal"
+ ]
+ },
+ "@std/internal@1.0.14": {
+ "integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7"
+ }
+ },
+ "workspace": {
+ "packageJson": {
+ "dependencies": [
+ "npm:@biomejs/biome@2.5.12",
+ "npm:@types/bun@1.4.0",
+ "npm:jsdom@^30.0.1",
+ "npm:lz-string@1.5.0",
+ "npm:solid-js@1.9.15",
+ "npm:typescript@7.0.2",
+ "npm:vite-plugin-solid@2.11.14",
+ "npm:vite@8.2.2",
+ "npm:vitest@5"
+ ]
+ }
+ }
+}
diff --git a/server/client.test.ts b/server/client.test.ts
new file mode 100644
index 0000000..1a972c6
--- /dev/null
+++ b/server/client.test.ts
@@ -0,0 +1,59 @@
+///
+import { assertEquals } from "jsr:@std/assert";
+
+const url = "ws://0.0.0.0:6969";
+
+function connect(): Promise {
+ return new Promise((resolve, reject) => {
+ const socket = new WebSocket(url);
+ socket.onopen = () => resolve(socket);
+ socket.onerror = (e) => reject(e);
+ });
+}
+
+function nextMessage(socket: WebSocket): Promise {
+ return new Promise((resolve, reject) => {
+ const onMessage = (e: MessageEvent) => {
+ socket.removeEventListener("message", onMessage);
+ try {
+ resolve(JSON.parse(e.data));
+ } catch {
+ resolve(e.data);
+ }
+ };
+ socket.addEventListener("message", onMessage);
+ socket.addEventListener("error", reject, { once: true });
+ });
+}
+
+function closeAndWait(socket: WebSocket): Promise {
+ return new Promise((resolve) => {
+ if (socket.readyState === WebSocket.CLOSED) return resolve();
+ socket.onclose = () => resolve();
+ socket.close();
+ });
+}
+
+Deno.test("broadcasts a message from one client to another", async () => {
+ const a = await connect(); // 1
+ const b = await connect(); // 2
+
+ const received = await nextMessage(b);
+ assertEquals(received, "2 has joined!");
+
+ await closeAndWait(a);
+ await closeAndWait(b);
+});
+
+Deno.test("removes a client from broadcast after it disconnects", async () => {
+ // test are executed in a row so the ID will just increment
+ const a = await connect(); // 3
+ const b = await connect(); // 4
+
+ await closeAndWait(b);
+
+ const msg = await nextMessage(a);
+ assertEquals(msg, "4 has left!");
+
+ await closeAndWait(a);
+});