a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 313 lines 7.4 kB view raw
1import { computed, effect, signal } from "$core/signal"; 2import { describe, expect, it, vi } from "vitest"; 3 4describe("signal", () => { 5 it("creates a signal with an initial value", () => { 6 const count = signal(0); 7 expect(count.get()).toBe(0); 8 }); 9 10 it("updates the value with set", () => { 11 const count = signal(0); 12 count.set(5); 13 expect(count.get()).toBe(5); 14 }); 15 16 it("notifies subscribers when value changes", () => { 17 const count = signal(0); 18 const subscriber = vi.fn(); 19 20 count.subscribe(subscriber); 21 count.set(10); 22 23 expect(subscriber).toHaveBeenCalledWith(10); 24 expect(subscriber).toHaveBeenCalledTimes(1); 25 }); 26 27 it("does not notify subscribers when value is the same", () => { 28 const count = signal(0); 29 const subscriber = vi.fn(); 30 31 count.subscribe(subscriber); 32 count.set(0); 33 34 expect(subscriber).not.toHaveBeenCalled(); 35 }); 36 37 it("supports multiple subscribers", () => { 38 const count = signal(0); 39 const subscriber1 = vi.fn(); 40 const subscriber2 = vi.fn(); 41 42 count.subscribe(subscriber1); 43 count.subscribe(subscriber2); 44 count.set(5); 45 46 expect(subscriber1).toHaveBeenCalledWith(5); 47 expect(subscriber2).toHaveBeenCalledWith(5); 48 }); 49 50 it("allows unsubscribing", () => { 51 const count = signal(0); 52 const subscriber = vi.fn(); 53 54 const unsubscribe = count.subscribe(subscriber); 55 unsubscribe(); 56 count.set(10); 57 58 expect(subscriber).not.toHaveBeenCalled(); 59 }); 60 61 it("notifies immediately on each update", () => { 62 const count = signal(0); 63 const subscriber = vi.fn(); 64 65 count.subscribe(subscriber); 66 67 count.set(1); 68 count.set(2); 69 count.set(3); 70 71 expect(subscriber).toHaveBeenCalledTimes(3); 72 expect(subscriber).toHaveBeenNthCalledWith(1, 1); 73 expect(subscriber).toHaveBeenNthCalledWith(2, 2); 74 expect(subscriber).toHaveBeenNthCalledWith(3, 3); 75 }); 76 77 it("handles object values", () => { 78 const object = signal({ count: 0 }); 79 const subscriber = vi.fn(); 80 81 object.subscribe(subscriber); 82 83 const newValue = { count: 1 }; 84 object.set(newValue); 85 86 expect(object.get()).toBe(newValue); 87 }); 88 89 it("handles array values", () => { 90 const array = signal([1, 2, 3]); 91 const subscriber = vi.fn(); 92 93 array.subscribe(subscriber); 94 95 const newValue = [4, 5, 6]; 96 array.set(newValue); 97 98 expect(array.get()).toEqual([4, 5, 6]); 99 }); 100 101 it("allows updating to null or undefined", () => { 102 const value = signal<string | null | undefined>("test"); 103 104 value.set(null); 105 expect(value.get()).toBe(null); 106 107 value.set(undefined); 108 expect(value.get()).toBe(undefined); 109 }); 110 111 it("handles rapid subscribe/unsubscribe", () => { 112 const count = signal(0); 113 const subscriber = vi.fn(); 114 115 const unsub = count.subscribe(subscriber); 116 unsub(); 117 count.subscribe(subscriber); 118 119 count.set(5); 120 121 expect(subscriber).toHaveBeenCalledTimes(1); 122 expect(subscriber).toHaveBeenCalledWith(5); 123 }); 124}); 125 126describe("computed", () => { 127 it("computes initial value", () => { 128 const count = signal(5); 129 const doubled = computed(() => count.get() * 2); 130 131 expect(doubled.get()).toBe(10); 132 }); 133 134 it("recomputes when dependency changes", () => { 135 const count = signal(5); 136 const doubled = computed(() => count.get() * 2); 137 138 expect(doubled.get()).toBe(10); 139 140 count.set(10); 141 expect(doubled.get()).toBe(20); 142 143 count.set(0); 144 expect(doubled.get()).toBe(0); 145 }); 146 147 it("notifies subscribers when value changes", () => { 148 const count = signal(5); 149 const doubled = computed(() => count.get() * 2); 150 const subscriber = vi.fn(); 151 152 doubled.subscribe(subscriber); 153 154 count.set(10); 155 expect(subscriber).toHaveBeenCalledWith(20); 156 expect(subscriber).toHaveBeenCalledTimes(1); 157 }); 158 159 it("does not notify when computed value is the same", () => { 160 const count = signal(5); 161 const isPositive = computed(() => count.get() > 0); 162 const subscriber = vi.fn(); 163 164 isPositive.subscribe(subscriber); 165 166 count.set(10); 167 expect(subscriber).not.toHaveBeenCalled(); 168 169 count.set(-1); 170 expect(subscriber).toHaveBeenCalledWith(false); 171 expect(subscriber).toHaveBeenCalledTimes(1); 172 }); 173 174 it("supports multiple dependencies", () => { 175 const a = signal(2); 176 const b = signal(3); 177 const sum = computed(() => a.get() + b.get()); 178 179 expect(sum.get()).toBe(5); 180 181 a.set(5); 182 expect(sum.get()).toBe(8); 183 184 b.set(10); 185 expect(sum.get()).toBe(15); 186 }); 187 188 it("can depend on other computed signals", () => { 189 const count = signal(2); 190 const doubled = computed(() => count.get() * 2); 191 const quadrupled = computed(() => doubled.get() * 2); 192 193 expect(quadrupled.get()).toBe(8); 194 195 count.set(5); 196 expect(doubled.get()).toBe(10); 197 expect(quadrupled.get()).toBe(20); 198 }); 199 200 it("allows unsubscribing", () => { 201 const count = signal(5); 202 const doubled = computed(() => count.get() * 2); 203 const subscriber = vi.fn(); 204 205 const unsubscribe = doubled.subscribe(subscriber); 206 unsubscribe(); 207 208 count.set(10); 209 expect(subscriber).not.toHaveBeenCalled(); 210 }); 211}); 212 213describe("effect", () => { 214 it("runs when dependency changes", () => { 215 const count = signal(0); 216 const effectFunction = vi.fn(() => { 217 count.get(); 218 }); 219 220 effect(effectFunction); 221 222 count.set(1); 223 count.set(2); 224 225 expect(effectFunction).toHaveBeenCalledTimes(3); 226 }); 227 228 it("can be cleaned up", () => { 229 const count = signal(0); 230 const effectFunction = vi.fn(() => { 231 count.get(); 232 }); 233 234 const cleanup = effect(effectFunction); 235 236 expect(effectFunction).toHaveBeenCalledTimes(1); 237 238 cleanup(); 239 240 count.set(1); 241 expect(effectFunction).toHaveBeenCalledTimes(1); 242 }); 243 244 it("runs cleanup function from previous effect", () => { 245 const count = signal(0); 246 const innerCleanup = vi.fn(); 247 const effectFunction = vi.fn(() => { 248 count.get(); 249 return innerCleanup; 250 }); 251 252 effect(effectFunction); 253 254 expect(innerCleanup).not.toHaveBeenCalled(); 255 256 count.set(1); 257 expect(innerCleanup).toHaveBeenCalledTimes(1); 258 259 count.set(2); 260 expect(innerCleanup).toHaveBeenCalledTimes(2); 261 }); 262 263 it("runs final cleanup when effect is disposed", () => { 264 const count = signal(0); 265 const innerCleanup = vi.fn(); 266 const effectFunction = vi.fn(() => { 267 count.get(); 268 return innerCleanup; 269 }); 270 271 const cleanup = effect(effectFunction); 272 273 count.set(1); 274 expect(innerCleanup).toHaveBeenCalledTimes(1); 275 276 cleanup(); 277 expect(innerCleanup).toHaveBeenCalledTimes(2); 278 }); 279 280 it("supports multiple dependencies", () => { 281 const a = signal(1); 282 const b = signal(2); 283 const effectFunction = vi.fn(() => { 284 a.get(); 285 b.get(); 286 }); 287 288 effect(effectFunction); 289 290 expect(effectFunction).toHaveBeenCalledTimes(1); 291 292 a.set(5); 293 expect(effectFunction).toHaveBeenCalledTimes(2); 294 295 b.set(10); 296 expect(effectFunction).toHaveBeenCalledTimes(3); 297 }); 298 299 it("can depend on computed signals", () => { 300 const count = signal(2); 301 const doubled = computed(() => count.get() * 2); 302 const effectFunction = vi.fn(() => { 303 doubled.get(); 304 }); 305 306 effect(effectFunction); 307 308 expect(effectFunction).toHaveBeenCalledTimes(1); 309 310 count.set(5); 311 expect(effectFunction).toHaveBeenCalledTimes(2); 312 }); 313});