@zeix/cause-effect 0.13.1 → 0.14.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.
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect, mock } from 'bun:test'
2
- import { state, computed, effect, UNSET } from '../'
2
+ import { state, task, effect, UNSET, memo } from '../'
3
3
 
4
4
  /* === Utility Functions === */
5
5
 
@@ -8,11 +8,11 @@ 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
- cause.tap(() => {
14
+ effect(() => {
15
+ cause.get()
16
16
  count++
17
17
  })
18
18
  expect(count).toBe(1)
@@ -20,12 +20,12 @@ describe('Effect', function () {
20
20
  expect(count).toBe(2)
21
21
  })
22
22
 
23
- test('should be triggered after computed async signals resolve without waterfalls', async function() {
24
- const a = computed(async () => {
23
+ test('should be triggered after computed async signals resolve without waterfalls', async function () {
24
+ const a = task(async () => {
25
25
  await wait(100)
26
26
  return 10
27
27
  })
28
- const b = computed(async () => {
28
+ const b = task(async () => {
29
29
  await wait(100)
30
30
  return 20
31
31
  })
@@ -36,7 +36,7 @@ describe('Effect', function () {
36
36
  ok: (aValue, bValue) => {
37
37
  result = aValue + bValue
38
38
  count++
39
- }
39
+ },
40
40
  })
41
41
  expect(result).toBe(0)
42
42
  expect(count).toBe(0)
@@ -45,30 +45,32 @@ describe('Effect', function () {
45
45
  expect(count).toBe(1)
46
46
  })
47
47
 
48
- test('should be triggered repeatedly after repeated state change', async function() {
48
+ test('should be triggered repeatedly after repeated state change', async function () {
49
49
  const cause = state(0)
50
50
  let result = 0
51
51
  let count = 0
52
- cause.tap(res => {
53
- result = res
52
+ effect(() => {
53
+ result = cause.get()
54
54
  count++
55
55
  })
56
56
  for (let i = 0; i < 10; i++) {
57
57
  cause.set(i)
58
58
  expect(result).toBe(i)
59
- expect(count).toBe(i + 1); // + 1 for effect initialization
59
+ expect(count).toBe(i + 1) // + 1 for effect initialization
60
60
  }
61
61
  })
62
62
 
63
- test('should handle errors in effects', function() {
63
+ test('should handle errors in effects', function () {
64
64
  const a = state(1)
65
- const b = a.map(v => {
65
+ const b = memo(() => {
66
+ const v = a.get()
66
67
  if (v > 5) throw new Error('Value too high')
67
68
  return v * 2
68
69
  })
69
70
  let normalCallCount = 0
70
71
  let errorCallCount = 0
71
- b.tap({
72
+ effect({
73
+ signals: [b],
72
74
  ok: () => {
73
75
  // console.log('Normal effect:', value)
74
76
  normalCallCount++
@@ -77,47 +79,48 @@ describe('Effect', function () {
77
79
  // console.log('Error effect:', error)
78
80
  errorCallCount++
79
81
  expect(error.message).toBe('Value too high')
80
- }
82
+ },
81
83
  })
82
-
84
+
83
85
  // Normal case
84
86
  a.set(2)
85
87
  expect(normalCallCount).toBe(2)
86
88
  expect(errorCallCount).toBe(0)
87
-
89
+
88
90
  // Error case
89
91
  a.set(6)
90
92
  expect(normalCallCount).toBe(2)
91
93
  expect(errorCallCount).toBe(1)
92
-
94
+
93
95
  // Back to normal
94
96
  a.set(3)
95
97
  expect(normalCallCount).toBe(3)
96
98
  expect(errorCallCount).toBe(1)
97
99
  })
98
100
 
99
- test('should handle UNSET values in effects', async function() {
100
- const a = computed(async () => {
101
+ test('should handle UNSET values in effects', async function () {
102
+ const a = task(async () => {
101
103
  await wait(100)
102
104
  return 42
103
105
  })
104
106
  let normalCallCount = 0
105
107
  let nilCount = 0
106
- a.tap({
108
+ effect({
109
+ signals: [a],
107
110
  ok: aValue => {
108
111
  normalCallCount++
109
112
  expect(aValue).toBe(42)
110
113
  },
111
114
  nil: () => {
112
115
  nilCount++
113
- }
116
+ },
114
117
  })
115
118
 
116
119
  expect(normalCallCount).toBe(0)
117
120
  expect(nilCount).toBe(1)
118
121
  expect(a.get()).toBe(UNSET)
119
122
  await wait(110)
120
- expect(normalCallCount).toBe(1)
123
+ expect(normalCallCount).toBeGreaterThan(0)
121
124
  expect(nilCount).toBe(1)
122
125
  expect(a.get()).toBe(42)
123
126
  })
@@ -130,26 +133,27 @@ describe('Effect', function () {
130
133
 
131
134
  try {
132
135
  const a = state(1)
133
- const b = a.map(v => {
136
+ const b = memo(() => {
137
+ const v = a.get()
134
138
  if (v > 5) throw new Error('Value too high')
135
139
  return v * 2
136
140
  })
137
141
 
138
142
  // Create an effect without explicit error handling
139
- b.tap(() => {})
143
+ effect(() => {
144
+ b.get()
145
+ })
140
146
 
141
147
  // This should trigger the error
142
148
  a.set(6)
143
149
 
144
150
  // Check if console.error was called with the error
145
- expect(mockConsoleError).toHaveBeenCalledWith(
146
- expect.any(Error)
147
- )
151
+ expect(mockConsoleError).toHaveBeenCalledWith(expect.any(Error))
148
152
 
149
153
  // Check the error message
150
- const error = (mockConsoleError as ReturnType<typeof mock>).mock.calls[0][0] as Error
154
+ const error = (mockConsoleError as ReturnType<typeof mock>).mock
155
+ .calls[0][0] as Error
151
156
  expect(error.message).toBe('Value too high')
152
-
153
157
  } finally {
154
158
  // Restore the original console.error
155
159
  console.error = originalConsoleError
@@ -160,8 +164,8 @@ describe('Effect', function () {
160
164
  const count = state(42)
161
165
  let received = 0
162
166
 
163
- const cleanup = count.tap(value => {
164
- received = value
167
+ const cleanup = effect(() => {
168
+ received = count.get()
165
169
  })
166
170
 
167
171
  count.set(43)
@@ -176,8 +180,9 @@ describe('Effect', function () {
176
180
  let okCount = 0
177
181
  let errCount = 0
178
182
  const count = state(0)
179
-
180
- count.tap({
183
+
184
+ effect({
185
+ signals: [count],
181
186
  ok: () => {
182
187
  okCount++
183
188
  // This effect updates the signal it depends on, creating a circular dependency
@@ -187,9 +192,9 @@ describe('Effect', function () {
187
192
  errCount++
188
193
  expect(e).toBeInstanceOf(Error)
189
194
  expect(e.message).toBe('Circular dependency in effect detected')
190
- }
195
+ },
191
196
  })
192
-
197
+
193
198
  // Verify that the count was changed only once due to the circular dependency error
194
199
  expect(count.get()).toBe(1)
195
200
  expect(okCount).toBe(1)
@@ -1,16 +1,11 @@
1
1
  import { describe, test, expect } from 'bun:test'
2
- import { isComputed, isState, state, UNSET } from '../'
3
-
4
- /* === Utility Functions === */
5
-
6
- const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
2
+ import { isComputed, isState, state } from '../'
7
3
 
8
4
  /* === Tests === */
9
5
 
10
6
  describe('State', function () {
11
-
12
- describe("State type guard", () => {
13
- test("isState identifies state signals", () => {
7
+ describe('State type guard', () => {
8
+ test('isState identifies state signals', () => {
14
9
  const count = state(42)
15
10
  expect(isState(count)).toBe(true)
16
11
  expect(isComputed(count)).toBe(false)
@@ -18,7 +13,6 @@ describe('State', function () {
18
13
  })
19
14
 
20
15
  describe('Boolean cause', function () {
21
-
22
16
  test('should be boolean', function () {
23
17
  const cause = state(false)
24
18
  expect(typeof cause.get()).toBe('boolean')
@@ -42,14 +36,12 @@ describe('State', function () {
42
36
 
43
37
  test('should toggle initial value with .set(v => !v)', function () {
44
38
  const cause = state(false)
45
- cause.update((v) => !v)
39
+ cause.update(v => !v)
46
40
  expect(cause.get()).toBe(true)
47
41
  })
48
-
49
42
  })
50
43
 
51
44
  describe('Number cause', function () {
52
-
53
45
  test('should be number', function () {
54
46
  const cause = state(0)
55
47
  expect(typeof cause.get()).toBe('number')
@@ -71,11 +63,9 @@ describe('State', function () {
71
63
  cause.update(v => ++v)
72
64
  expect(cause.get()).toBe(1)
73
65
  })
74
-
75
66
  })
76
67
 
77
68
  describe('String cause', function () {
78
-
79
69
  test('should be string', function () {
80
70
  const cause = state('foo')
81
71
  expect(typeof cause.get()).toBe('string')
@@ -94,14 +84,12 @@ describe('State', function () {
94
84
 
95
85
  test('should upper case value with .set(v => v.toUpperCase())', function () {
96
86
  const cause = state('foo')
97
- cause.update(v => v ? v.toUpperCase() : '')
98
- expect(cause.get()).toBe("FOO")
87
+ cause.update(v => (v ? v.toUpperCase() : ''))
88
+ expect(cause.get()).toBe('FOO')
99
89
  })
100
-
101
90
  })
102
91
 
103
92
  describe('Array cause', function () {
104
-
105
93
  test('should be array', function () {
106
94
  const cause = state([1, 2, 3])
107
95
  expect(Array.isArray(cause.get())).toBe(true)
@@ -121,21 +109,19 @@ describe('State', function () {
121
109
  test('should reflect current value of array after modification', function () {
122
110
  const array = [1, 2, 3]
123
111
  const cause = state(array)
124
- array.push(4); // don't do this! the result will be correct, but we can't trigger effects
112
+ array.push(4) // don't do this! the result will be correct, but we can't trigger effects
125
113
  expect(cause.get()).toEqual([1, 2, 3, 4])
126
114
  })
127
115
 
128
116
  test('should set new value with .set([...array, 4])', function () {
129
117
  const array = [1, 2, 3]
130
118
  const cause = state(array)
131
- cause.set([...array, 4]); // use destructuring instead!
119
+ cause.set([...array, 4]) // use destructuring instead!
132
120
  expect(cause.get()).toEqual([1, 2, 3, 4])
133
121
  })
134
-
135
122
  })
136
123
 
137
124
  describe('Object cause', function () {
138
-
139
125
  test('should be object', function () {
140
126
  const cause = state({ a: 'a', b: 1 })
141
127
  expect(typeof cause.get()).toBe('object')
@@ -155,69 +141,16 @@ describe('State', function () {
155
141
  test('should reflect current value of object after modification', function () {
156
142
  const obj = { a: 'a', b: 1 }
157
143
  const cause = state<Record<string, any>>(obj)
158
- // @ts-expect-error
159
- obj.c = true; // don't do this! the result will be correct, but we can't trigger effects
144
+ // @ts-expect-error Property 'c' does not exist on type '{ a: string; b: number; }'. (ts 2339)
145
+ obj.c = true // don't do this! the result will be correct, but we can't trigger effects
160
146
  expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
161
147
  })
162
148
 
163
149
  test('should set new value with .set({...obj, c: true})', function () {
164
150
  const obj = { a: 'a', b: 1 }
165
151
  const cause = state<Record<string, any>>(obj)
166
- cause.set({...obj, c: true}); // use destructuring instead!
152
+ cause.set({ ...obj, c: true }) // use destructuring instead!
167
153
  expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
168
154
  })
169
-
170
155
  })
171
-
172
- describe('Map method', function () {
173
-
174
- test('should return a computed signal', function() {
175
- const cause = state(42)
176
- const double = cause.map(v => v * 2)
177
- expect(isComputed(double)).toBe(true)
178
- expect(double.get()).toBe(84)
179
- })
180
-
181
- test('should return a computed signal for an async function', async function() {
182
- const cause = state(42)
183
- const asyncDouble = cause.map(async value => {
184
- await wait(100)
185
- return value * 2
186
- })
187
- expect(isComputed(asyncDouble)).toBe(true)
188
- expect(asyncDouble.get()).toBe(UNSET)
189
- await wait(110)
190
- expect(asyncDouble.get()).toBe(84)
191
- })
192
-
193
- })
194
-
195
- describe('Tap method', function () {
196
-
197
- test('should create an effect that reacts on signal changes', function() {
198
- const cause = state(42)
199
- let okCount = 0
200
- let nilCount = 0
201
- let result = 0
202
- cause.tap({
203
- ok: v => {
204
- result = v
205
- okCount++
206
- },
207
- nil: () => {
208
- nilCount++
209
- }
210
- })
211
- cause.set(43)
212
- expect(okCount).toBe(2); // + 1 for effect initialization
213
- expect(nilCount).toBe(0)
214
- expect(result).toBe(43)
215
-
216
- cause.set(UNSET)
217
- expect(okCount).toBe(2)
218
- expect(nilCount).toBe(1)
219
- })
220
-
221
- })
222
-
223
- });
156
+ })