a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 8.5 kB view raw
1import { reactive } from "$core/reactive"; 2import { getAllSignals, getReactiveInfo, getSignalInfo } from "$debug/registry"; 3import { attachDebugger, debugComputed, debugReactive, debugSignal, vdebugger } from "$vebug"; 4import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 5 6describe("debug API", () => { 7 let _consoleSpy: { log: ReturnType<typeof vi.spyOn> }; 8 9 beforeEach(() => { 10 vdebugger.clear(); 11 _consoleSpy = { log: vi.spyOn(console, "log").mockImplementation(() => {}) }; 12 }); 13 14 afterEach(() => { 15 vi.restoreAllMocks(); 16 }); 17 18 describe("debugSignal", () => { 19 it("creates a signal and registers it", () => { 20 const sig = debugSignal(42, "answer"); 21 expect(sig.get()).toBe(42); 22 23 const info = getSignalInfo(sig); 24 expect(info).toBeDefined(); 25 expect(info?.name).toBe("answer"); 26 expect(info?.type).toBe("signal"); 27 }); 28 29 it("works without a name", () => { 30 const sig = debugSignal(0); 31 expect(sig.get()).toBe(0); 32 33 const info = getSignalInfo(sig); 34 expect(info).toBeDefined(); 35 expect(info?.name).toBeUndefined(); 36 }); 37 38 it("returns standard Signal interface", () => { 39 const sig = debugSignal(0); 40 expect(sig.get).toBeTypeOf("function"); 41 expect(sig.set).toBeTypeOf("function"); 42 expect(sig.subscribe).toBeTypeOf("function"); 43 44 sig.set(5); 45 expect(sig.get()).toBe(5); 46 47 const callback = vi.fn(); 48 const unsubscribe = sig.subscribe(callback); 49 sig.set(10); 50 expect(callback).toHaveBeenCalledWith(10); 51 52 unsubscribe(); 53 }); 54 }); 55 56 describe("debugComputed", () => { 57 it("creates a computed signal and registers it", () => { 58 const count = debugSignal(5); 59 const doubled = debugComputed(() => count.get() * 2, "doubled"); 60 expect(doubled.get()).toBe(10); 61 62 const info = getSignalInfo(doubled); 63 expect(info).toBeDefined(); 64 expect(info?.name).toBe("doubled"); 65 expect(info?.type).toBe("computed"); 66 }); 67 68 it("works without a name", () => { 69 const count = debugSignal(5); 70 const doubled = debugComputed(() => count.get() * 2); 71 expect(doubled.get()).toBe(10); 72 73 const info = getSignalInfo(doubled); 74 expect(info).toBeDefined(); 75 }); 76 77 it("recomputes when dependencies change", () => { 78 const count = debugSignal(5); 79 const doubled = debugComputed(() => count.get() * 2); 80 expect(doubled.get()).toBe(10); 81 82 count.set(10); 83 expect(doubled.get()).toBe(20); 84 }); 85 }); 86 87 describe("debugReactive", () => { 88 it("creates a reactive object and registers it", () => { 89 const state = debugReactive({ count: 42 }, "state"); 90 expect(state.count).toBe(42); 91 92 const info = getReactiveInfo(state); 93 expect(info).toBeDefined(); 94 expect(info?.name).toBe("state"); 95 expect(info?.type).toBe("reactive"); 96 }); 97 98 it("works without a name", () => { 99 const state = debugReactive({ count: 0 }); 100 expect(state.count).toBe(0); 101 102 const info = getReactiveInfo(state); 103 expect(info).toBeDefined(); 104 }); 105 106 it("maintains reactivity", () => { 107 const state = debugReactive({ count: 0 }); 108 const doubled = debugComputed(() => state.count * 2); 109 expect(doubled.get()).toBe(0); 110 111 state.count = 5; 112 expect(doubled.get()).toBe(10); 113 }); 114 }); 115 116 describe("attachDebugger", () => { 117 it("registers existing signal", () => { 118 const sig = debugSignal(0); 119 const info = getSignalInfo(sig); 120 expect(info).toBeDefined(); 121 }); 122 123 it("does not re-register already registered signal", () => { 124 const sig = debugSignal(0, "original"); 125 attachDebugger(sig, "signal", "new"); 126 127 const info = getSignalInfo(sig); 128 expect(info?.name).toBe("original"); 129 }); 130 }); 131 132 describe("vdebugger namespace", () => { 133 it("provides signal creation", () => { 134 const sig = vdebugger.signal(42, "answer"); 135 expect(sig.get()).toBe(42); 136 137 const info = getSignalInfo(sig); 138 expect(info?.name).toBe("answer"); 139 }); 140 141 it("provides computed creation", () => { 142 const count = vdebugger.signal(5); 143 const doubled = vdebugger.computed(() => count.get() * 2, "doubled"); 144 expect(doubled.get()).toBe(10); 145 }); 146 147 it("provides reactive creation", () => { 148 const state = vdebugger.reactive({ count: 0 }, "state"); 149 expect(state.count).toBe(0); 150 }); 151 152 it("provides getAllSignals", () => { 153 const sig1 = vdebugger.signal(1); 154 const sig2 = vdebugger.signal(2); 155 const all = vdebugger.getAllSignals(); 156 expect(all).toHaveLength(2); 157 expect(all).toContain(sig1); 158 expect(all).toContain(sig2); 159 }); 160 161 it("provides getAllReactives", () => { 162 const obj1 = vdebugger.reactive({ a: 1 }); 163 const obj2 = vdebugger.reactive({ b: 2 }); 164 const all = vdebugger.getAllReactives(); 165 expect(all).toHaveLength(2); 166 expect(all).toContain(obj1); 167 expect(all).toContain(obj2); 168 }); 169 170 it("provides getSignalInfo", () => { 171 const sig = vdebugger.signal(42, "answer"); 172 const info = vdebugger.getSignalInfo(sig); 173 expect(info).toBeDefined(); 174 expect(info?.name).toBe("answer"); 175 expect(info?.value).toBe(42); 176 }); 177 178 it("provides getReactiveInfo", () => { 179 const obj = vdebugger.reactive({ count: 42 }, "state"); 180 const info = vdebugger.getReactiveInfo(obj); 181 expect(info).toBeDefined(); 182 expect(info?.name).toBe("state"); 183 }); 184 185 it("provides stats", () => { 186 vdebugger.signal(1); 187 vdebugger.signal(2); 188 vdebugger.computed(() => 3); 189 vdebugger.reactive({}); 190 191 const stats = vdebugger.getStats(); 192 expect(stats.totalSignals).toBe(3); 193 expect(stats.regularSignals).toBe(2); 194 expect(stats.computedSignals).toBe(1); 195 expect(stats.reactiveObjects).toBe(1); 196 }); 197 198 it("provides naming functions", () => { 199 const sig = vdebugger.signal(0); 200 vdebugger.nameSignal(sig, "renamed"); 201 202 const info = vdebugger.getSignalInfo(sig); 203 expect(info?.name).toBe("renamed"); 204 }); 205 206 it("provides graph operations", () => { 207 const a = vdebugger.signal(1, "a"); 208 const b = vdebugger.signal(2, "b"); 209 210 expect(vdebugger.getDependencies(a)).toEqual([]); 211 expect(vdebugger.getDependents(a)).toEqual([]); 212 expect(vdebugger.getDepth(a)).toBe(0); 213 214 const graph = vdebugger.buildGraph([a, b]); 215 expect(graph.nodes).toHaveLength(2); 216 }); 217 218 it("provides logging functions", () => { 219 const sig = vdebugger.signal(42, "answer"); 220 expect(() => vdebugger.log(sig)).not.toThrow(); 221 expect(() => vdebugger.logAll()).not.toThrow(); 222 expect(() => vdebugger.logTable()).not.toThrow(); 223 }); 224 225 it("provides tracing functions", () => { 226 const sig = vdebugger.signal(0, "count"); 227 expect(() => vdebugger.trace(sig)).not.toThrow(); 228 expect(() => vdebugger.enableTracing()).not.toThrow(); 229 expect(() => vdebugger.disableTracing()).not.toThrow(); 230 }); 231 232 it("provides watch function", () => { 233 const sig = vdebugger.signal(0, "count"); 234 const unwatch = vdebugger.watch(sig); 235 expect(unwatch).toBeTypeOf("function"); 236 unwatch(); 237 }); 238 239 it("provides clear function", () => { 240 vdebugger.signal(1); 241 vdebugger.signal(2); 242 243 expect(getAllSignals()).toHaveLength(2); 244 245 vdebugger.clear(); 246 247 expect(getAllSignals()).toHaveLength(0); 248 }); 249 250 it("provides attach function", () => { 251 const sig = debugSignal(0); 252 vdebugger.attach(sig, "signal", "attached"); 253 254 const info = vdebugger.getSignalInfo(sig); 255 expect(info).toBeDefined(); 256 }); 257 }); 258 259 describe("integration", () => { 260 it("works with non-debug core primitives", () => { 261 const coreReactive = reactive({ count: 0 }); 262 const debugCount = debugSignal(5); 263 const sum = debugComputed(() => coreReactive.count + debugCount.get(), "sum"); 264 expect(sum.get()).toBe(5); 265 266 coreReactive.count = 10; 267 expect(sum.get()).toBe(15); 268 269 debugCount.set(20); 270 expect(sum.get()).toBe(30); 271 }); 272 273 it("allows mixing debug and non-debug signals", () => { 274 const debug = debugSignal(1, "debug"); 275 const regular = debugSignal(2); 276 const all = getAllSignals(); 277 expect(all).toHaveLength(2); 278 expect(all).toContain(debug); 279 expect(all).toContain(regular); 280 }); 281 }); 282});