@zeix/cause-effect 0.13.0 → 0.13.2

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.
@@ -4,12 +4,11 @@ import { state, computed, UNSET, isComputed, isState } from '../index.ts'
4
4
  /* === Utility Functions === */
5
5
 
6
6
  const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
7
- const increment = (n: number) => Number.isFinite(n) ? n + 1 : UNSET
7
+ const increment = (n: number) => (Number.isFinite(n) ? n + 1 : UNSET)
8
8
 
9
9
  /* === Tests === */
10
10
 
11
11
  describe('Computed', function () {
12
-
13
12
  test('should identify computed signals with isComputed()', () => {
14
13
  const count = state(42)
15
14
  const doubled = count.map(v => v * 2)
@@ -17,24 +16,24 @@ describe('Computed', function () {
17
16
  expect(isState(doubled)).toBe(false)
18
17
  })
19
18
 
20
- test('should compute a function', function() {
19
+ test('should compute a function', function () {
21
20
  const derived = computed(() => 1 + 2)
22
21
  expect(derived.get()).toBe(3)
23
22
  })
24
23
 
25
- test('should compute function dependent on a signal', function() {
24
+ test('should compute function dependent on a signal', function () {
26
25
  const derived = state(42).map(v => ++v)
27
26
  expect(derived.get()).toBe(43)
28
27
  })
29
28
 
30
- test('should compute function dependent on an updated signal', function() {
29
+ test('should compute function dependent on an updated signal', function () {
31
30
  const cause = state(42)
32
31
  const derived = cause.map(v => ++v)
33
32
  cause.set(24)
34
33
  expect(derived.get()).toBe(25)
35
34
  })
36
35
 
37
- test('should compute function dependent on an async signal', async function() {
36
+ test('should compute function dependent on an async signal', async function () {
38
37
  const status = state('pending')
39
38
  const promised = computed(async () => {
40
39
  await wait(100)
@@ -49,7 +48,7 @@ describe('Computed', function () {
49
48
  expect(status.get()).toBe('success')
50
49
  })
51
50
 
52
- test('should handle errors from an async signal gracefully', async function() {
51
+ test('should handle errors from an async signal gracefully', async function () {
53
52
  const status = state('pending')
54
53
  const error = state('')
55
54
  const promised = computed(async () => {
@@ -66,7 +65,7 @@ describe('Computed', function () {
66
65
  expect(status.get()).toBe('error')
67
66
  })
68
67
 
69
- test('should compute async signals in parallel without waterfalls', async function() {
68
+ test('should compute async signals in parallel without waterfalls', async function () {
70
69
  const a = computed(async () => {
71
70
  await wait(100)
72
71
  return 10
@@ -77,14 +76,14 @@ describe('Computed', function () {
77
76
  })
78
77
  const c = computed({
79
78
  signals: [a, b],
80
- ok: (aValue, bValue) => aValue + bValue
79
+ ok: (aValue, bValue) => aValue + bValue,
81
80
  })
82
81
  expect(c.get()).toBe(UNSET)
83
82
  await wait(110)
84
83
  expect(c.get()).toBe(30)
85
84
  })
86
85
 
87
- test('should compute function dependent on a chain of computed states dependent on a signal', function() {
86
+ test('should compute function dependent on a chain of computed states dependent on a signal', function () {
88
87
  const derived = state(42)
89
88
  .map(v => ++v)
90
89
  .map(v => v * 2)
@@ -92,7 +91,7 @@ describe('Computed', function () {
92
91
  expect(derived.get()).toBe(87)
93
92
  })
94
93
 
95
- test('should compute function dependent on a chain of computed states dependent on an updated signal', function() {
94
+ test('should compute function dependent on a chain of computed states dependent on an updated signal', function () {
96
95
  const cause = state(42)
97
96
  const derived = cause
98
97
  .map(v => ++v)
@@ -118,7 +117,7 @@ describe('Computed', function () {
118
117
  expect(count).toBe(2)
119
118
  })
120
119
 
121
- test('should only update every signal once (diamond graph)', function() {
120
+ test('should only update every signal once (diamond graph)', function () {
122
121
  let count = 0
123
122
  const x = state('a')
124
123
  const a = x.map(v => v)
@@ -135,7 +134,7 @@ describe('Computed', function () {
135
134
  expect(count).toBe(2)
136
135
  })
137
136
 
138
- test('should only update every signal once (diamond graph + tail)', function() {
137
+ test('should only update every signal once (diamond graph + tail)', function () {
139
138
  let count = 0
140
139
  const x = state('a')
141
140
  const a = x.map(v => v)
@@ -152,7 +151,7 @@ describe('Computed', function () {
152
151
  expect(count).toBe(2)
153
152
  })
154
153
 
155
- test('should update multiple times after multiple state changes', function() {
154
+ test('should update multiple times after multiple state changes', function () {
156
155
  const a = state(3)
157
156
  const b = state(4)
158
157
  let count = 0
@@ -170,19 +169,21 @@ describe('Computed', function () {
170
169
 
171
170
  /*
172
171
  * Note for the next two tests:
173
- *
172
+ *
174
173
  * Due to the lazy evaluation strategy, unchanged computed signals may propagate
175
174
  * change notifications one additional time before stabilizing. This is a
176
175
  * one-time performance cost that allows for efficient memoization and
177
176
  * error handling in most cases.
178
177
  */
179
- test('should bail out if result is the same', function() {
178
+ test('should bail out if result is the same', function () {
180
179
  let count = 0
181
180
  const x = state('a')
182
- const b = x.map(() => 'foo').map(v => {
183
- count++
184
- return v
185
- })
181
+ const b = x
182
+ .map(() => 'foo')
183
+ .map(v => {
184
+ count++
185
+ return v
186
+ })
186
187
  expect(b.get()).toBe('foo')
187
188
  expect(count).toBe(1)
188
189
  x.set('aa')
@@ -192,11 +193,12 @@ describe('Computed', function () {
192
193
  expect(count).toBe(2)
193
194
  })
194
195
 
195
- test('should block if result remains unchanged', function() {
196
+ test('should block if result remains unchanged', function () {
196
197
  let count = 0
197
198
  const x = state(42)
198
- const c = x.map(v => v % 2)
199
- .map(v => v ? 'odd' : 'even')
199
+ const c = x
200
+ .map(v => v % 2)
201
+ .map(v => (v ? 'odd' : 'even'))
200
202
  .map(v => {
201
203
  count++
202
204
  return `c: ${v}`
@@ -210,7 +212,7 @@ describe('Computed', function () {
210
212
  expect(count).toBe(2)
211
213
  })
212
214
 
213
- test('should detect and throw error for circular dependencies', function() {
215
+ test('should detect and throw error for circular dependencies', function () {
214
216
  const a = state(1)
215
217
  const b = computed(() => c.get() + 1)
216
218
  const c = computed(() => b.get() + a.get())
@@ -220,7 +222,7 @@ describe('Computed', function () {
220
222
  expect(a.get()).toBe(1)
221
223
  })
222
224
 
223
- test('should propagate error if an error occurred', function() {
225
+ test('should propagate error if an error occurred', function () {
224
226
  let okCount = 0
225
227
  let errCount = 0
226
228
  const x = state(0)
@@ -230,7 +232,7 @@ describe('Computed', function () {
230
232
  })
231
233
  const c = computed({
232
234
  signals: [a],
233
- ok: v => v ? 'success' : 'failure',
235
+ ok: v => (v ? 'success' : 'failure'),
234
236
  err: () => {
235
237
  errCount++
236
238
  return 'recovered'
@@ -245,7 +247,7 @@ describe('Computed', function () {
245
247
  try {
246
248
  x.set(1)
247
249
  expect(a.get()).toBe(1)
248
- expect(true).toBe(false); // This line should not be reached
250
+ expect(true).toBe(false) // This line should not be reached
249
251
  } catch (error) {
250
252
  expect(error.message).toBe('Calculation error')
251
253
  } finally {
@@ -255,7 +257,7 @@ describe('Computed', function () {
255
257
  }
256
258
  })
257
259
 
258
- test('should return a computed signal with .map()', function() {
260
+ test('should return a computed signal with .map()', function () {
259
261
  const cause = state(42)
260
262
  const derived = cause.map(v => ++v)
261
263
  const double = derived.map(v => v * 2)
@@ -263,7 +265,7 @@ describe('Computed', function () {
263
265
  expect(double.get()).toBe(86)
264
266
  })
265
267
 
266
- test('should create an effect that reacts on async computed changes with .tap()', async function() {
268
+ test('should create an effect that reacts on async computed changes with .tap()', async function () {
267
269
  const cause = state(42)
268
270
  const derived = computed(async () => {
269
271
  await wait(100)
@@ -279,7 +281,7 @@ describe('Computed', function () {
279
281
  },
280
282
  nil: () => {
281
283
  nilCount++
282
- }
284
+ },
283
285
  })
284
286
  cause.set(43)
285
287
  expect(okCount).toBe(0)
@@ -292,7 +294,7 @@ describe('Computed', function () {
292
294
  expect(result).toBe(44)
293
295
  })
294
296
 
295
- test('should handle complex computed signal with error and async dependencies', async function() {
297
+ test('should handle complex computed signal with error and async dependencies', async function () {
296
298
  const toggleState = state(true)
297
299
  const errorProne = toggleState.map(v => {
298
300
  if (v) throw new Error('Intentional error')
@@ -319,9 +321,9 @@ describe('Computed', function () {
319
321
  err: () => {
320
322
  errCount++
321
323
  return -1
322
- }
324
+ },
323
325
  })
324
-
326
+
325
327
  /* computed(() => {
326
328
  try {
327
329
  const x = errorProne.get()
@@ -338,21 +340,21 @@ describe('Computed', function () {
338
340
  return -1
339
341
  }
340
342
  }) */
341
-
343
+
342
344
  for (let i = 0; i < 10; i++) {
343
345
  toggleState.set(!!(i % 2))
344
346
  await wait(10)
345
347
  result = complexComputed.get()
346
348
  // console.log(`i: ${i}, result: ${result}`)
347
349
  }
348
-
350
+
349
351
  expect(nilCount).toBeGreaterThanOrEqual(5)
350
352
  expect(okCount).toBeGreaterThanOrEqual(2)
351
353
  expect(errCount).toBeGreaterThanOrEqual(3)
352
354
  expect(okCount + errCount + nilCount).toBe(10)
353
355
  })
354
356
 
355
- test('should handle signal changes during async computation', async function() {
357
+ test('should handle signal changes during async computation', async function () {
356
358
  const source = state(1)
357
359
  let computationCount = 0
358
360
  const derived = computed(async abort => {
@@ -373,7 +375,7 @@ describe('Computed', function () {
373
375
  expect(computationCount).toBe(1)
374
376
  })
375
377
 
376
- test('should handle multiple rapid changes during async computation', async function() {
378
+ test('should handle multiple rapid changes during async computation', async function () {
377
379
  const source = state(1)
378
380
  let computationCount = 0
379
381
  const derived = computed(async abort => {
@@ -398,7 +400,7 @@ describe('Computed', function () {
398
400
  expect(computationCount).toBe(1)
399
401
  })
400
402
 
401
- test('should handle errors in aborted computations', async function() {
403
+ test('should handle errors in aborted computations', async function () {
402
404
  // const startTime = performance.now()
403
405
  const source = state(1)
404
406
  const derived = computed(async () => {
@@ -407,7 +409,7 @@ describe('Computed', function () {
407
409
  if (value === 2) throw new Error('Intentional error')
408
410
  return value
409
411
  })
410
-
412
+
411
413
  /* derived.tap({
412
414
  ok: v => {
413
415
  console.log(`ok: ${v}, time: ${performance.now() - startTime}ms`)
@@ -427,11 +429,10 @@ describe('Computed', function () {
427
429
  source.set(2)
428
430
  await wait(110)
429
431
  expect(() => derived.get()).toThrow('Intentional error')
430
-
432
+
431
433
  // Change to normal state before second computation completes
432
434
  source.set(3)
433
435
  await wait(100)
434
436
  expect(derived.get()).toBe(3)
435
-
436
437
  })
437
- })
438
+ })
@@ -8,8 +8,7 @@ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
8
8
  /* === Tests === */
9
9
 
10
10
  describe('Effect', function () {
11
-
12
- test('should be triggered after a state change', function() {
11
+ test('should be triggered after a state change', function () {
13
12
  const cause = state('foo')
14
13
  let count = 0
15
14
  cause.tap(() => {
@@ -20,7 +19,7 @@ describe('Effect', function () {
20
19
  expect(count).toBe(2)
21
20
  })
22
21
 
23
- test('should be triggered after computed async signals resolve without waterfalls', async function() {
22
+ test('should be triggered after computed async signals resolve without waterfalls', async function () {
24
23
  const a = computed(async () => {
25
24
  await wait(100)
26
25
  return 10
@@ -36,7 +35,7 @@ describe('Effect', function () {
36
35
  ok: (aValue, bValue) => {
37
36
  result = aValue + bValue
38
37
  count++
39
- }
38
+ },
40
39
  })
41
40
  expect(result).toBe(0)
42
41
  expect(count).toBe(0)
@@ -45,7 +44,7 @@ describe('Effect', function () {
45
44
  expect(count).toBe(1)
46
45
  })
47
46
 
48
- test('should be triggered repeatedly after repeated state change', async function() {
47
+ test('should be triggered repeatedly after repeated state change', async function () {
49
48
  const cause = state(0)
50
49
  let result = 0
51
50
  let count = 0
@@ -56,11 +55,11 @@ describe('Effect', function () {
56
55
  for (let i = 0; i < 10; i++) {
57
56
  cause.set(i)
58
57
  expect(result).toBe(i)
59
- expect(count).toBe(i + 1); // + 1 for effect initialization
58
+ expect(count).toBe(i + 1) // + 1 for effect initialization
60
59
  }
61
60
  })
62
61
 
63
- test('should handle errors in effects', function() {
62
+ test('should handle errors in effects', function () {
64
63
  const a = state(1)
65
64
  const b = a.map(v => {
66
65
  if (v > 5) throw new Error('Value too high')
@@ -77,26 +76,26 @@ describe('Effect', function () {
77
76
  // console.log('Error effect:', error)
78
77
  errorCallCount++
79
78
  expect(error.message).toBe('Value too high')
80
- }
79
+ },
81
80
  })
82
-
81
+
83
82
  // Normal case
84
83
  a.set(2)
85
84
  expect(normalCallCount).toBe(2)
86
85
  expect(errorCallCount).toBe(0)
87
-
86
+
88
87
  // Error case
89
88
  a.set(6)
90
89
  expect(normalCallCount).toBe(2)
91
90
  expect(errorCallCount).toBe(1)
92
-
91
+
93
92
  // Back to normal
94
93
  a.set(3)
95
94
  expect(normalCallCount).toBe(3)
96
95
  expect(errorCallCount).toBe(1)
97
96
  })
98
97
 
99
- test('should handle UNSET values in effects', async function() {
98
+ test('should handle UNSET values in effects', async function () {
100
99
  const a = computed(async () => {
101
100
  await wait(100)
102
101
  return 42
@@ -110,7 +109,7 @@ describe('Effect', function () {
110
109
  },
111
110
  nil: () => {
112
111
  nilCount++
113
- }
112
+ },
114
113
  })
115
114
 
116
115
  expect(normalCallCount).toBe(0)
@@ -142,14 +141,12 @@ describe('Effect', function () {
142
141
  a.set(6)
143
142
 
144
143
  // Check if console.error was called with the error
145
- expect(mockConsoleError).toHaveBeenCalledWith(
146
- expect.any(Error)
147
- )
144
+ expect(mockConsoleError).toHaveBeenCalledWith(expect.any(Error))
148
145
 
149
146
  // Check the error message
150
- const error = (mockConsoleError as ReturnType<typeof mock>).mock.calls[0][0] as Error
147
+ const error = (mockConsoleError as ReturnType<typeof mock>).mock
148
+ .calls[0][0] as Error
151
149
  expect(error.message).toBe('Value too high')
152
-
153
150
  } finally {
154
151
  // Restore the original console.error
155
152
  console.error = originalConsoleError
@@ -176,7 +173,7 @@ describe('Effect', function () {
176
173
  let okCount = 0
177
174
  let errCount = 0
178
175
  const count = state(0)
179
-
176
+
180
177
  count.tap({
181
178
  ok: () => {
182
179
  okCount++
@@ -187,9 +184,9 @@ describe('Effect', function () {
187
184
  errCount++
188
185
  expect(e).toBeInstanceOf(Error)
189
186
  expect(e.message).toBe('Circular dependency in effect detected')
190
- }
187
+ },
191
188
  })
192
-
189
+
193
190
  // Verify that the count was changed only once due to the circular dependency error
194
191
  expect(count.get()).toBe(1)
195
192
  expect(okCount).toBe(1)
@@ -8,9 +8,8 @@ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
8
8
  /* === Tests === */
9
9
 
10
10
  describe('State', function () {
11
-
12
- describe("State type guard", () => {
13
- test("isState identifies state signals", () => {
11
+ describe('State type guard', () => {
12
+ test('isState identifies state signals', () => {
14
13
  const count = state(42)
15
14
  expect(isState(count)).toBe(true)
16
15
  expect(isComputed(count)).toBe(false)
@@ -18,7 +17,6 @@ describe('State', function () {
18
17
  })
19
18
 
20
19
  describe('Boolean cause', function () {
21
-
22
20
  test('should be boolean', function () {
23
21
  const cause = state(false)
24
22
  expect(typeof cause.get()).toBe('boolean')
@@ -42,14 +40,12 @@ describe('State', function () {
42
40
 
43
41
  test('should toggle initial value with .set(v => !v)', function () {
44
42
  const cause = state(false)
45
- cause.update((v) => !v)
43
+ cause.update(v => !v)
46
44
  expect(cause.get()).toBe(true)
47
45
  })
48
-
49
46
  })
50
47
 
51
48
  describe('Number cause', function () {
52
-
53
49
  test('should be number', function () {
54
50
  const cause = state(0)
55
51
  expect(typeof cause.get()).toBe('number')
@@ -71,11 +67,9 @@ describe('State', function () {
71
67
  cause.update(v => ++v)
72
68
  expect(cause.get()).toBe(1)
73
69
  })
74
-
75
70
  })
76
71
 
77
72
  describe('String cause', function () {
78
-
79
73
  test('should be string', function () {
80
74
  const cause = state('foo')
81
75
  expect(typeof cause.get()).toBe('string')
@@ -94,14 +88,12 @@ describe('State', function () {
94
88
 
95
89
  test('should upper case value with .set(v => v.toUpperCase())', function () {
96
90
  const cause = state('foo')
97
- cause.update(v => v ? v.toUpperCase() : '')
98
- expect(cause.get()).toBe("FOO")
91
+ cause.update(v => (v ? v.toUpperCase() : ''))
92
+ expect(cause.get()).toBe('FOO')
99
93
  })
100
-
101
94
  })
102
95
 
103
96
  describe('Array cause', function () {
104
-
105
97
  test('should be array', function () {
106
98
  const cause = state([1, 2, 3])
107
99
  expect(Array.isArray(cause.get())).toBe(true)
@@ -121,21 +113,19 @@ describe('State', function () {
121
113
  test('should reflect current value of array after modification', function () {
122
114
  const array = [1, 2, 3]
123
115
  const cause = state(array)
124
- array.push(4); // don't do this! the result will be correct, but we can't trigger effects
116
+ array.push(4) // don't do this! the result will be correct, but we can't trigger effects
125
117
  expect(cause.get()).toEqual([1, 2, 3, 4])
126
118
  })
127
119
 
128
120
  test('should set new value with .set([...array, 4])', function () {
129
121
  const array = [1, 2, 3]
130
122
  const cause = state(array)
131
- cause.set([...array, 4]); // use destructuring instead!
123
+ cause.set([...array, 4]) // use destructuring instead!
132
124
  expect(cause.get()).toEqual([1, 2, 3, 4])
133
125
  })
134
-
135
126
  })
136
127
 
137
128
  describe('Object cause', function () {
138
-
139
129
  test('should be object', function () {
140
130
  const cause = state({ a: 'a', b: 1 })
141
131
  expect(typeof cause.get()).toBe('object')
@@ -156,29 +146,27 @@ describe('State', function () {
156
146
  const obj = { a: 'a', b: 1 }
157
147
  const cause = state<Record<string, any>>(obj)
158
148
  // @ts-expect-error
159
- obj.c = true; // don't do this! the result will be correct, but we can't trigger effects
149
+ obj.c = true // don't do this! the result will be correct, but we can't trigger effects
160
150
  expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
161
151
  })
162
152
 
163
153
  test('should set new value with .set({...obj, c: true})', function () {
164
154
  const obj = { a: 'a', b: 1 }
165
155
  const cause = state<Record<string, any>>(obj)
166
- cause.set({...obj, c: true}); // use destructuring instead!
156
+ cause.set({ ...obj, c: true }) // use destructuring instead!
167
157
  expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
168
158
  })
169
-
170
159
  })
171
160
 
172
161
  describe('Map method', function () {
173
-
174
- test('should return a computed signal', function() {
162
+ test('should return a computed signal', function () {
175
163
  const cause = state(42)
176
164
  const double = cause.map(v => v * 2)
177
165
  expect(isComputed(double)).toBe(true)
178
166
  expect(double.get()).toBe(84)
179
167
  })
180
168
 
181
- test('should return a computed signal for an async function', async function() {
169
+ test('should return a computed signal for an async function', async function () {
182
170
  const cause = state(42)
183
171
  const asyncDouble = cause.map(async value => {
184
172
  await wait(100)
@@ -189,12 +177,10 @@ describe('State', function () {
189
177
  await wait(110)
190
178
  expect(asyncDouble.get()).toBe(84)
191
179
  })
192
-
193
180
  })
194
181
 
195
182
  describe('Tap method', function () {
196
-
197
- test('should create an effect that reacts on signal changes', function() {
183
+ test('should create an effect that reacts on signal changes', function () {
198
184
  const cause = state(42)
199
185
  let okCount = 0
200
186
  let nilCount = 0
@@ -206,10 +192,10 @@ describe('State', function () {
206
192
  },
207
193
  nil: () => {
208
194
  nilCount++
209
- }
195
+ },
210
196
  })
211
197
  cause.set(43)
212
- expect(okCount).toBe(2); // + 1 for effect initialization
198
+ expect(okCount).toBe(2) // + 1 for effect initialization
213
199
  expect(nilCount).toBe(0)
214
200
  expect(result).toBe(43)
215
201
 
@@ -217,7 +203,5 @@ describe('State', function () {
217
203
  expect(okCount).toBe(2)
218
204
  expect(nilCount).toBe(1)
219
205
  })
220
-
221
206
  })
222
-
223
- });
207
+ })