signalium 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,497 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { state, computed, asyncComputed } from './utils/instrumented.js';
3
+ import { AsyncResult } from '../signals';
4
+
5
+ const sleep = (ms = 0) => new Promise(r => setTimeout(r, ms));
6
+ const nextTick = () => new Promise(r => setTimeout(r, 0));
7
+
8
+ const result = <T>(
9
+ value: T | undefined,
10
+ promiseState: 'pending' | 'error' | 'success',
11
+ isReady: boolean,
12
+ ): AsyncResult<T> =>
13
+ ({
14
+ result: value,
15
+ error: undefined,
16
+ isPending: promiseState === 'pending',
17
+ isReady,
18
+ isError: promiseState === 'error',
19
+ isSuccess: promiseState === 'success',
20
+ }) as AsyncResult<T>;
21
+
22
+ describe.skip('Async Signal functionality', () => {
23
+ test('Can run basic computed', async () => {
24
+ const a = state(1);
25
+ const b = state(2);
26
+
27
+ const c = asyncComputed(async () => {
28
+ return a.get() + b.get();
29
+ });
30
+
31
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
32
+ compute: 1,
33
+ resolve: 0,
34
+ });
35
+
36
+ await nextTick();
37
+
38
+ expect(c).toHaveValueAndCounts(result(3, 'success', true), {
39
+ compute: 1,
40
+ resolve: 1,
41
+ });
42
+
43
+ // stability
44
+ expect(c).toHaveValueAndCounts(result(3, 'success', true), {
45
+ compute: 1,
46
+ resolve: 1,
47
+ });
48
+ });
49
+
50
+ test('Computeds can be updated', async () => {
51
+ const a = state(1);
52
+ const b = state(2);
53
+
54
+ const c = asyncComputed(async () => {
55
+ return a.get() + b.get();
56
+ });
57
+
58
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
59
+ compute: 1,
60
+ resolve: 0,
61
+ });
62
+
63
+ await nextTick();
64
+
65
+ expect(c).toHaveValueAndCounts(result(3, 'success', true), {
66
+ compute: 1,
67
+ resolve: 1,
68
+ });
69
+
70
+ a.set(2);
71
+
72
+ expect(c).toHaveValueAndCounts(result(3, 'pending', true), {
73
+ compute: 2,
74
+ resolve: 1,
75
+ });
76
+
77
+ await nextTick();
78
+
79
+ expect(c).toHaveValueAndCounts(result(4, 'success', true), {
80
+ compute: 2,
81
+ resolve: 2,
82
+ });
83
+ });
84
+
85
+ test('Does not update if value is the same', async () => {
86
+ const a = state(1);
87
+ const b = state(2);
88
+
89
+ const c = asyncComputed(async () => {
90
+ return a.get() + b.get();
91
+ });
92
+
93
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
94
+ compute: 1,
95
+ resolve: 0,
96
+ });
97
+
98
+ await nextTick();
99
+
100
+ expect(c).toHaveValueAndCounts(result(3, 'success', true), {
101
+ compute: 1,
102
+ resolve: 1,
103
+ });
104
+
105
+ a.set(1);
106
+
107
+ expect(c).toHaveValueAndCounts(result(3, 'success', true), {
108
+ compute: 1,
109
+ resolve: 1,
110
+ });
111
+
112
+ await nextTick();
113
+
114
+ expect(c).toHaveValueAndCounts(result(3, 'success', true), {
115
+ compute: 1,
116
+ resolve: 1,
117
+ });
118
+ });
119
+
120
+ test('Skips resolution if value is updated multiple times', async () => {
121
+ const a = state(1);
122
+ const b = state(2);
123
+
124
+ const c = asyncComputed(async () => {
125
+ const result = a.get() + b.get();
126
+
127
+ if (result === 4) {
128
+ await sleep(100);
129
+ }
130
+
131
+ return result;
132
+ });
133
+
134
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
135
+ compute: 1,
136
+ resolve: 0,
137
+ });
138
+
139
+ await nextTick();
140
+
141
+ expect(c).toHaveValueAndCounts(result(3, 'success', true), {
142
+ compute: 1,
143
+ resolve: 1,
144
+ });
145
+
146
+ a.set(2);
147
+
148
+ expect(c).toHaveValueAndCounts(result(3, 'pending', true), {
149
+ compute: 2,
150
+ resolve: 1,
151
+ });
152
+
153
+ a.set(3);
154
+
155
+ expect(c).toHaveValueAndCounts(result(3, 'pending', true), {
156
+ compute: 3,
157
+ resolve: 1,
158
+ });
159
+
160
+ await sleep(200);
161
+
162
+ expect(c).toHaveValueAndCounts(result(5, 'success', true), {
163
+ compute: 3,
164
+ resolve: 3,
165
+ });
166
+ });
167
+
168
+ describe('Nesting', () => {
169
+ test('Can nest computeds', () => {
170
+ const a = state(1);
171
+ const b = state(2);
172
+ const c = state(2);
173
+
174
+ const inner = computed(() => {
175
+ return a.get() + b.get();
176
+ });
177
+
178
+ const outer = computed(() => {
179
+ return inner.get() + c.get();
180
+ });
181
+
182
+ expect(inner).toHaveValueAndCounts(3, { compute: 1 });
183
+ expect(outer).toHaveValueAndCounts(5, { compute: 1 });
184
+
185
+ // stability
186
+ expect(inner).toHaveValueAndCounts(3, { compute: 1 });
187
+ expect(outer).toHaveValueAndCounts(5, { compute: 1 });
188
+ });
189
+
190
+ test('Can dirty inner computed and update parent', () => {
191
+ const a = state(1);
192
+ const b = state(2);
193
+ const c = state(2);
194
+
195
+ const inner = computed(() => {
196
+ return a.get() + b.get();
197
+ });
198
+
199
+ const outer = computed(() => {
200
+ return inner.get() + c.get();
201
+ });
202
+
203
+ expect(inner).toHaveValueAndCounts(3, { compute: 1 });
204
+ expect(outer).toHaveValueAndCounts(5, { compute: 1 });
205
+
206
+ a.set(2);
207
+
208
+ expect(inner).toHaveValueAndCounts(4, { compute: 2 });
209
+ expect(outer).toHaveValueAndCounts(6, { compute: 2 });
210
+ });
211
+
212
+ test('Can dirty outer computed and inner stays cached', () => {
213
+ const a = state(1);
214
+ const b = state(2);
215
+ const c = state(2);
216
+
217
+ const inner = computed(() => {
218
+ return a.get() + b.get();
219
+ });
220
+
221
+ const outer = computed(() => {
222
+ return inner.get() + c.get();
223
+ });
224
+
225
+ expect(inner).toHaveValueAndCounts(3, { compute: 1 });
226
+ expect(outer).toHaveValueAndCounts(5, { compute: 1 });
227
+
228
+ c.set(3);
229
+
230
+ expect(inner).toHaveValueAndCounts(3, { compute: 1 });
231
+ expect(outer).toHaveValueAndCounts(6, { compute: 2 });
232
+ });
233
+
234
+ test('Can nest multiple levels', () => {
235
+ const a = state(1);
236
+ const b = state(2);
237
+ const c = state(2);
238
+ const d = state(2);
239
+
240
+ const inner = computed(() => {
241
+ return a.get() + b.get();
242
+ });
243
+
244
+ const mid = computed(() => {
245
+ return inner.get() + c.get();
246
+ });
247
+
248
+ const outer = computed(() => {
249
+ return mid.get() + d.get();
250
+ });
251
+
252
+ expect(inner).toHaveValueAndCounts(3, { compute: 1 });
253
+ expect(mid).toHaveValueAndCounts(5, { compute: 1 });
254
+ expect(outer).toHaveValueAndCounts(7, { compute: 1 });
255
+
256
+ a.set(2);
257
+
258
+ expect(inner).toHaveValueAndCounts(4, { compute: 2 });
259
+ expect(mid).toHaveValueAndCounts(6, { compute: 2 });
260
+ expect(outer).toHaveValueAndCounts(8, { compute: 2 });
261
+
262
+ c.set(3);
263
+
264
+ expect(inner).toHaveValueAndCounts(4, { compute: 2 });
265
+ expect(mid).toHaveValueAndCounts(7, { compute: 3 });
266
+ expect(outer).toHaveValueAndCounts(9, { compute: 3 });
267
+
268
+ d.set(3);
269
+
270
+ expect(inner).toHaveValueAndCounts(4, { compute: 2 });
271
+ expect(mid).toHaveValueAndCounts(7, { compute: 3 });
272
+ expect(outer).toHaveValueAndCounts(10, { compute: 4 });
273
+ });
274
+ });
275
+
276
+ describe('Propagation', () => {
277
+ test('it works with multiple parents', () => {
278
+ const a = state(1);
279
+ const b = state(2);
280
+ const c = state(2);
281
+ const d = state(2);
282
+
283
+ const inner = computed(() => {
284
+ return a.get() + b.get();
285
+ });
286
+
287
+ const outer1 = computed(() => {
288
+ return inner.get() + c.get();
289
+ });
290
+
291
+ const outer2 = computed(() => {
292
+ return inner.get() + d.get();
293
+ });
294
+
295
+ expect(inner).toHaveValueAndCounts(3, { compute: 1 });
296
+ expect(outer1).toHaveValueAndCounts(5, { compute: 1 });
297
+ expect(outer2).toHaveValueAndCounts(5, { compute: 1 });
298
+
299
+ a.set(2);
300
+
301
+ expect(inner).toHaveValueAndCounts(4, { compute: 2 });
302
+ expect(outer1).toHaveValueAndCounts(6, { compute: 2 });
303
+ expect(outer2).toHaveValueAndCounts(6, { compute: 2 });
304
+
305
+ b.set(3);
306
+
307
+ expect(inner).toHaveValueAndCounts(5, { compute: 3 });
308
+ expect(outer2).toHaveValueAndCounts(7, { compute: 3 });
309
+ expect(outer1).toHaveValueAndCounts(7, { compute: 3 });
310
+
311
+ c.set(3);
312
+
313
+ expect(inner).toHaveValueAndCounts(5, { compute: 3 });
314
+ expect(outer1).toHaveValueAndCounts(8, { compute: 4 });
315
+ expect(outer2).toHaveValueAndCounts(7, { compute: 3 });
316
+
317
+ d.set(3);
318
+
319
+ expect(inner).toHaveValueAndCounts(5, { compute: 3 });
320
+ expect(outer1).toHaveValueAndCounts(8, { compute: 4 });
321
+ expect(outer2).toHaveValueAndCounts(8, { compute: 4 });
322
+ });
323
+
324
+ test('it stops propagation if the result is the same', () => {
325
+ const a = state(1);
326
+ const b = state(2);
327
+ const c = state(2);
328
+ const d = state(2);
329
+
330
+ const inner = computed('inner', () => {
331
+ return a.get() + b.get();
332
+ });
333
+
334
+ const outer1 = computed('outer1', () => {
335
+ return inner.get() + c.get();
336
+ });
337
+
338
+ const outer2 = computed('outer2', () => {
339
+ return inner.get() + d.get();
340
+ });
341
+
342
+ expect(() => {
343
+ expect(outer1).toHaveValueAndCounts(5, { compute: 1 });
344
+ expect(outer2).toHaveValueAndCounts(5, { compute: 1 });
345
+ expect(inner).toHaveValueAndCounts(3, { compute: 1 });
346
+ }).toHaveComputedOrder(['outer1', 'inner', 'outer2']);
347
+
348
+ a.set(2);
349
+ b.set(1);
350
+
351
+ expect(() => {
352
+ expect(outer1).toHaveValueAndCounts(5, { compute: 1 });
353
+ expect(outer2).toHaveValueAndCounts(5, { compute: 1 });
354
+ expect(inner).toHaveValueAndCounts(3, { compute: 2 });
355
+ }).toHaveComputedOrder(['inner']);
356
+
357
+ b.set(2);
358
+
359
+ expect(() => {
360
+ expect(outer2).toHaveValueAndCounts(6, { compute: 2 });
361
+ expect(outer1).toHaveValueAndCounts(6, { compute: 2 });
362
+ expect(inner).toHaveValueAndCounts(4, { compute: 3 });
363
+ }).toHaveComputedOrder(['inner', 'outer2', 'outer1']);
364
+ });
365
+
366
+ test('it continues propagation if any child is different', () => {
367
+ const a = state(1);
368
+ const b = state(2);
369
+ const c = state(2);
370
+ const d = state(2);
371
+
372
+ const inner1 = computed('inner1', () => {
373
+ return a.get() + b.get();
374
+ });
375
+
376
+ const inner2 = computed('inner2', () => {
377
+ return c.get();
378
+ });
379
+
380
+ const inner3 = computed('inner3', () => {
381
+ return d.get();
382
+ });
383
+
384
+ const outer = computed('outer', () => {
385
+ return inner1.get() + inner2.get() + inner3.get();
386
+ });
387
+
388
+ expect(() => {
389
+ expect(outer).toHaveValueAndCounts(7, { compute: 1 });
390
+ }).toHaveComputedOrder(['outer', 'inner1', 'inner2', 'inner3']);
391
+
392
+ d.set(4);
393
+ a.set(2);
394
+ c.set(3);
395
+ b.set(1);
396
+
397
+ expect(() => {
398
+ expect(outer).toHaveValueAndCounts(10, { compute: 2 });
399
+ expect(inner1).toHaveCounts({ compute: 2 });
400
+ expect(inner2).toHaveCounts({ compute: 2 });
401
+ }).toHaveComputedOrder(['inner1', 'inner2', 'outer', 'inner3']);
402
+ });
403
+ });
404
+
405
+ describe('Laziness', () => {
406
+ test('it does not compute values that are not used', () => {
407
+ const a = state(1);
408
+ const b = state(2);
409
+ const c = state(2);
410
+ const d = state(2);
411
+
412
+ const inner1 = computed(() => {
413
+ return a.get() + b.get();
414
+ });
415
+
416
+ const inner2 = computed(() => {
417
+ return c.get() + d.get();
418
+ });
419
+
420
+ const outer = computed(() => {
421
+ if (inner1.get() <= 3) {
422
+ return inner2.get();
423
+ } else {
424
+ return -1;
425
+ }
426
+ });
427
+
428
+ expect(outer).toHaveValueAndCounts(4, { compute: 1 });
429
+
430
+ a.set(2);
431
+ c.set(3);
432
+
433
+ expect(outer).toHaveValueAndCounts(-1, { compute: 2 });
434
+ expect(inner1).toHaveCounts({ compute: 2 });
435
+ expect(inner2).toHaveCounts({ compute: 1 });
436
+ });
437
+ });
438
+
439
+ describe('Equality', () => {
440
+ test('Does not update if value is the same (custom equality fn)', () => {
441
+ const a = state(1);
442
+ const b = state(2);
443
+
444
+ const c = computed(
445
+ () => {
446
+ return a.get() + b.get();
447
+ },
448
+ {
449
+ equals(prev, next) {
450
+ return Math.abs(prev - next) < 2;
451
+ },
452
+ },
453
+ );
454
+
455
+ expect(c).toHaveValueAndCounts(3, { compute: 1 });
456
+
457
+ a.set(2);
458
+
459
+ expect(c).toHaveValueAndCounts(3, { compute: 2 });
460
+ });
461
+
462
+ test('It stops propagation if the result is the same (custom equality fn)', () => {
463
+ const a = state(1);
464
+ const b = state(2);
465
+ const c = state(2);
466
+ const d = state(2);
467
+
468
+ const inner = computed(
469
+ () => {
470
+ return a.get() + b.get();
471
+ },
472
+ {
473
+ equals(prev, next) {
474
+ return Math.abs(prev - next) < 2;
475
+ },
476
+ },
477
+ );
478
+
479
+ const outer1 = computed(() => {
480
+ return inner.get() + c.get();
481
+ });
482
+
483
+ const outer2 = computed(() => {
484
+ return inner.get() + d.get();
485
+ });
486
+
487
+ expect(inner).toHaveValueAndCounts(3, { compute: 1 });
488
+
489
+ a.set(2);
490
+ b.set(2);
491
+
492
+ expect(inner).toHaveValueAndCounts(3, { compute: 2 });
493
+ expect(outer1).toHaveValueAndCounts(5, { compute: 1 });
494
+ expect(outer2).toHaveValueAndCounts(5, { compute: 1 });
495
+ });
496
+ });
497
+ });