@zeix/cause-effect 0.16.1 → 0.17.1
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.
- package/.ai-context.md +85 -21
- package/.cursorrules +11 -5
- package/.github/copilot-instructions.md +64 -13
- package/CLAUDE.md +143 -163
- package/LICENSE +1 -1
- package/README.md +248 -333
- package/archive/benchmark.ts +688 -0
- package/archive/collection.ts +312 -0
- package/{src → archive}/computed.ts +21 -21
- package/archive/list.ts +551 -0
- package/archive/memo.ts +139 -0
- package/{src → archive}/state.ts +13 -11
- package/archive/store.ts +368 -0
- package/archive/task.ts +194 -0
- package/eslint.config.js +1 -0
- package/index.dev.js +938 -509
- package/index.js +1 -1
- package/index.ts +50 -23
- package/package.json +1 -1
- package/src/classes/collection.ts +282 -0
- package/src/classes/composite.ts +176 -0
- package/src/classes/computed.ts +333 -0
- package/src/classes/list.ts +305 -0
- package/src/classes/ref.ts +68 -0
- package/src/classes/state.ts +98 -0
- package/src/classes/store.ts +210 -0
- package/src/diff.ts +26 -53
- package/src/effect.ts +9 -9
- package/src/errors.ts +71 -25
- package/src/match.ts +5 -12
- package/src/resolve.ts +3 -2
- package/src/signal.ts +58 -41
- package/src/system.ts +79 -42
- package/src/util.ts +16 -34
- package/test/batch.test.ts +15 -17
- package/test/benchmark.test.ts +4 -4
- package/test/collection.test.ts +853 -0
- package/test/computed.test.ts +138 -130
- package/test/diff.test.ts +2 -2
- package/test/effect.test.ts +36 -35
- package/test/list.test.ts +754 -0
- package/test/match.test.ts +25 -25
- package/test/ref.test.ts +227 -0
- package/test/resolve.test.ts +17 -19
- package/test/signal.test.ts +70 -119
- package/test/state.test.ts +44 -44
- package/test/store.test.ts +253 -929
- package/types/index.d.ts +12 -9
- package/types/src/classes/collection.d.ts +46 -0
- package/types/src/classes/composite.d.ts +15 -0
- package/types/src/classes/computed.d.ts +97 -0
- package/types/src/classes/list.d.ts +41 -0
- package/types/src/classes/ref.d.ts +39 -0
- package/types/src/classes/state.d.ts +52 -0
- package/types/src/classes/store.d.ts +51 -0
- package/types/src/diff.d.ts +8 -12
- package/types/src/errors.d.ts +17 -11
- package/types/src/signal.d.ts +27 -14
- package/types/src/system.d.ts +41 -20
- package/types/src/util.d.ts +6 -4
- package/src/store.ts +0 -474
- package/types/src/collection.d.ts +0 -26
- package/types/src/computed.d.ts +0 -33
- package/types/src/scheduler.d.ts +0 -55
- package/types/src/state.d.ts +0 -24
- package/types/src/store.d.ts +0 -65
package/test/signal.test.ts
CHANGED
|
@@ -1,51 +1,50 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import {
|
|
3
3
|
type Computed,
|
|
4
|
-
|
|
5
|
-
createState,
|
|
6
|
-
createStore,
|
|
4
|
+
createSignal,
|
|
7
5
|
isComputed,
|
|
6
|
+
isList,
|
|
8
7
|
isState,
|
|
9
8
|
isStore,
|
|
9
|
+
type List,
|
|
10
10
|
type Signal,
|
|
11
|
-
|
|
11
|
+
State,
|
|
12
12
|
type Store,
|
|
13
|
-
toSignal,
|
|
14
13
|
type UnknownRecord,
|
|
15
|
-
} from '
|
|
14
|
+
} from '../index.ts'
|
|
16
15
|
|
|
17
16
|
/* === Tests === */
|
|
18
17
|
|
|
19
|
-
describe('
|
|
18
|
+
describe('createSignal', () => {
|
|
20
19
|
describe('type inference and runtime behavior', () => {
|
|
21
|
-
test('converts array to
|
|
22
|
-
const result =
|
|
20
|
+
test('converts array to List<T>', () => {
|
|
21
|
+
const result = createSignal([
|
|
23
22
|
{ id: 1, name: 'Alice' },
|
|
24
23
|
{ id: 2, name: 'Bob' },
|
|
25
24
|
])
|
|
26
25
|
|
|
27
26
|
// Runtime behavior
|
|
28
|
-
expect(
|
|
29
|
-
expect(result
|
|
30
|
-
expect(result
|
|
27
|
+
expect(isList(result)).toBe(true)
|
|
28
|
+
expect(result.at(0)?.get()).toEqual({ id: 1, name: 'Alice' })
|
|
29
|
+
expect(result.at(1)?.get()).toEqual({ id: 2, name: 'Bob' })
|
|
31
30
|
|
|
32
|
-
// Type inference test - now correctly returns
|
|
33
|
-
const typedResult:
|
|
31
|
+
// Type inference test - now correctly returns List<{ id: number; name: string }>
|
|
32
|
+
const typedResult: List<{ id: number; name: string }> = result
|
|
34
33
|
expect(typedResult).toBeDefined()
|
|
35
34
|
})
|
|
36
35
|
|
|
37
36
|
test('converts empty array to ArrayStore<never[]>', () => {
|
|
38
|
-
const result =
|
|
37
|
+
const result = createSignal([])
|
|
39
38
|
|
|
40
39
|
// Runtime behavior
|
|
41
|
-
expect(
|
|
40
|
+
expect(isList(result)).toBe(true)
|
|
42
41
|
expect(result.length).toBe(0)
|
|
43
42
|
expect(Object.keys(result).length).toBe(0)
|
|
44
43
|
})
|
|
45
44
|
|
|
46
45
|
test('converts record to Store<T>', () => {
|
|
47
46
|
const record = { name: 'Alice', age: 30 }
|
|
48
|
-
const result =
|
|
47
|
+
const result = createSignal(record)
|
|
49
48
|
|
|
50
49
|
// Runtime behavior
|
|
51
50
|
expect(isStore(result)).toBe(true)
|
|
@@ -57,51 +56,9 @@ describe('toSignal', () => {
|
|
|
57
56
|
expect(typedResult).toBeDefined()
|
|
58
57
|
})
|
|
59
58
|
|
|
60
|
-
test('passes through existing Store unchanged', () => {
|
|
61
|
-
const originalStore = createStore({ count: 5 })
|
|
62
|
-
const result = toSignal(originalStore)
|
|
63
|
-
|
|
64
|
-
// Runtime behavior
|
|
65
|
-
expect(result).toBe(originalStore) // Should be the same instance
|
|
66
|
-
expect(isStore(result)).toBe(true)
|
|
67
|
-
expect(result.count.get()).toBe(5)
|
|
68
|
-
|
|
69
|
-
// Type inference test - should remain Store<{count: number}>
|
|
70
|
-
const typedResult: Store<{ count: number }> = result
|
|
71
|
-
expect(typedResult).toBeDefined()
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test('passes through existing State unchanged', () => {
|
|
75
|
-
const originalState = createState(42)
|
|
76
|
-
const result = toSignal(originalState)
|
|
77
|
-
|
|
78
|
-
// Runtime behavior
|
|
79
|
-
expect(result).toBe(originalState) // Should be the same instance
|
|
80
|
-
expect(isState(result)).toBe(true)
|
|
81
|
-
expect(result.get()).toBe(42)
|
|
82
|
-
|
|
83
|
-
// Type inference test - should remain State<number>
|
|
84
|
-
const typedResult: State<number> = result
|
|
85
|
-
expect(typedResult).toBeDefined()
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
test('passes through existing Computed unchanged', () => {
|
|
89
|
-
const originalComputed = createComputed(() => 'hello world')
|
|
90
|
-
const result = toSignal(originalComputed)
|
|
91
|
-
|
|
92
|
-
// Runtime behavior
|
|
93
|
-
expect(result).toBe(originalComputed) // Should be the same instance
|
|
94
|
-
expect(isComputed(result)).toBe(true)
|
|
95
|
-
expect(result.get()).toBe('hello world')
|
|
96
|
-
|
|
97
|
-
// Type inference test - should remain Computed<string>
|
|
98
|
-
const typedResult: Computed<string> = result
|
|
99
|
-
expect(typedResult).toBeDefined()
|
|
100
|
-
})
|
|
101
|
-
|
|
102
59
|
test('converts function to Computed<T>', () => {
|
|
103
60
|
const fn = () => Math.random()
|
|
104
|
-
const result =
|
|
61
|
+
const result = createSignal(fn)
|
|
105
62
|
|
|
106
63
|
// Runtime behavior - functions are correctly converted to Computed
|
|
107
64
|
expect(isComputed(result)).toBe(true)
|
|
@@ -114,7 +71,7 @@ describe('toSignal', () => {
|
|
|
114
71
|
|
|
115
72
|
test('converts primitive to State<T>', () => {
|
|
116
73
|
const num = 42
|
|
117
|
-
const result =
|
|
74
|
+
const result = createSignal(num)
|
|
118
75
|
|
|
119
76
|
// Runtime behavior - primitives are correctly converted to State
|
|
120
77
|
expect(isState(result)).toBe(true)
|
|
@@ -127,7 +84,7 @@ describe('toSignal', () => {
|
|
|
127
84
|
|
|
128
85
|
test('converts object to State<T>', () => {
|
|
129
86
|
const obj = new Date('2024-01-01')
|
|
130
|
-
const result =
|
|
87
|
+
const result = createSignal(obj)
|
|
131
88
|
|
|
132
89
|
// Runtime behavior - objects are correctly converted to State
|
|
133
90
|
expect(isState(result)).toBe(true)
|
|
@@ -141,15 +98,15 @@ describe('toSignal', () => {
|
|
|
141
98
|
|
|
142
99
|
describe('edge cases', () => {
|
|
143
100
|
test('handles nested arrays', () => {
|
|
144
|
-
const result =
|
|
101
|
+
const result = createSignal([
|
|
145
102
|
[1, 2],
|
|
146
103
|
[3, 4],
|
|
147
104
|
])
|
|
148
105
|
|
|
149
|
-
expect(
|
|
106
|
+
expect(isList(result)).toBe(true)
|
|
150
107
|
// With the fixed behavior, nested arrays should be recovered as arrays
|
|
151
|
-
const firstElement = result
|
|
152
|
-
const secondElement = result
|
|
108
|
+
const firstElement = result.at(0)?.get()
|
|
109
|
+
const secondElement = result.at(1)?.get()
|
|
153
110
|
|
|
154
111
|
// The expected behavior - nested arrays are recovered as arrays
|
|
155
112
|
expect(firstElement).toEqual([1, 2])
|
|
@@ -158,34 +115,23 @@ describe('toSignal', () => {
|
|
|
158
115
|
|
|
159
116
|
test('handles arrays with mixed types', () => {
|
|
160
117
|
const mixedArr = [1, 'hello', { key: 'value' }]
|
|
161
|
-
const result =
|
|
162
|
-
|
|
163
|
-
expect(isStore(result)).toBe(true)
|
|
164
|
-
expect(result['0'].get()).toBe(1)
|
|
165
|
-
expect(result['1'].get()).toBe('hello')
|
|
166
|
-
expect(result['2'].get()).toEqual({ key: 'value' })
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
test('handles sparse arrays', () => {
|
|
170
|
-
const sparseArr = new Array(3)
|
|
171
|
-
sparseArr[1] = 'middle'
|
|
172
|
-
const result = toSignal(sparseArr)
|
|
118
|
+
const result = createSignal(mixedArr)
|
|
173
119
|
|
|
174
|
-
expect(
|
|
175
|
-
expect(
|
|
176
|
-
expect(result
|
|
177
|
-
expect(
|
|
120
|
+
expect(isList(result)).toBe(true)
|
|
121
|
+
expect(result.at(0)?.get()).toBe(1)
|
|
122
|
+
expect(result.at(1)?.get()).toBe('hello')
|
|
123
|
+
expect(result.at(2)?.get()).toEqual({ key: 'value' })
|
|
178
124
|
})
|
|
179
125
|
})
|
|
180
126
|
})
|
|
181
127
|
|
|
182
128
|
describe('Signal compatibility', () => {
|
|
183
129
|
test('all results implement Signal<T> interface', () => {
|
|
184
|
-
const arraySignal =
|
|
185
|
-
const recordSignal =
|
|
186
|
-
const primitiveSignal =
|
|
187
|
-
const functionSignal =
|
|
188
|
-
const stateSignal =
|
|
130
|
+
const arraySignal = createSignal([1, 2, 3])
|
|
131
|
+
const recordSignal = createSignal({ a: 1, b: 2 })
|
|
132
|
+
const primitiveSignal = createSignal(42)
|
|
133
|
+
const functionSignal = createSignal(() => 'hello')
|
|
134
|
+
const stateSignal = createSignal(new State(true))
|
|
189
135
|
|
|
190
136
|
// All should have get() method
|
|
191
137
|
expect(typeof arraySignal.get).toBe('function')
|
|
@@ -210,16 +156,16 @@ describe('Type precision tests', () => {
|
|
|
210
156
|
test('array type should infer element type correctly', () => {
|
|
211
157
|
// Test that arrays infer the correct element type
|
|
212
158
|
const stringArray = ['a', 'b', 'c']
|
|
213
|
-
const stringArraySignal =
|
|
159
|
+
const stringArraySignal = createSignal(stringArray)
|
|
214
160
|
|
|
215
|
-
// Should be
|
|
216
|
-
expect(stringArraySignal
|
|
161
|
+
// Should be List<string>
|
|
162
|
+
expect(stringArraySignal.at(0)?.get()).toBe('a')
|
|
217
163
|
|
|
218
164
|
const numberArray = [1, 2, 3]
|
|
219
|
-
const numberArraySignal =
|
|
165
|
+
const numberArraySignal = createSignal(numberArray)
|
|
220
166
|
|
|
221
|
-
// Should be
|
|
222
|
-
expect(typeof numberArraySignal
|
|
167
|
+
// Should be List<number>
|
|
168
|
+
expect(typeof numberArraySignal.at(0)?.get()).toBe('number')
|
|
223
169
|
})
|
|
224
170
|
|
|
225
171
|
test('complex object arrays maintain precise typing', () => {
|
|
@@ -234,31 +180,31 @@ describe('Type precision tests', () => {
|
|
|
234
180
|
{ id: 2, name: 'Bob', email: 'bob@example.com' },
|
|
235
181
|
]
|
|
236
182
|
|
|
237
|
-
const usersSignal =
|
|
183
|
+
const usersSignal = createSignal(users)
|
|
238
184
|
|
|
239
185
|
// Should maintain User type for each element
|
|
240
|
-
const firstUser = usersSignal
|
|
241
|
-
expect(firstUser
|
|
242
|
-
expect(firstUser
|
|
243
|
-
expect(firstUser
|
|
186
|
+
const firstUser = usersSignal.at(0)?.get()
|
|
187
|
+
expect(firstUser?.id).toBe(1)
|
|
188
|
+
expect(firstUser?.name).toBe('Alice')
|
|
189
|
+
expect(firstUser?.email).toBe('alice@example.com')
|
|
244
190
|
})
|
|
245
191
|
|
|
246
192
|
describe('Type inference issues', () => {
|
|
247
193
|
test('demonstrates current type inference problem', () => {
|
|
248
|
-
const result =
|
|
194
|
+
const result = createSignal([{ id: 1 }, { id: 2 }])
|
|
249
195
|
|
|
250
196
|
// Let's verify the actual behavior
|
|
251
|
-
expect(
|
|
252
|
-
expect(result
|
|
253
|
-
expect(result
|
|
197
|
+
expect(isList(result)).toBe(true)
|
|
198
|
+
expect(result.at(0)?.get()).toEqual({ id: 1 })
|
|
199
|
+
expect(result.at(1)?.get()).toEqual({ id: 2 })
|
|
254
200
|
|
|
255
201
|
// Type assertion test - this should now work with correct typing
|
|
256
|
-
const typedResult:
|
|
202
|
+
const typedResult: List<{ id: number }> = result
|
|
257
203
|
expect(typedResult).toBeDefined()
|
|
258
204
|
|
|
259
205
|
// Simulate external library usage where P[K] represents element type
|
|
260
206
|
interface ExternalLibraryConstraint<P extends UnknownRecord> {
|
|
261
|
-
process<K extends keyof P>(signal: Signal<P[K]>): void
|
|
207
|
+
process<K extends keyof P>(signal: Signal<P[K] & {}>): void
|
|
262
208
|
}
|
|
263
209
|
|
|
264
210
|
// This should work if types are correct
|
|
@@ -266,7 +212,7 @@ describe('Type precision tests', () => {
|
|
|
266
212
|
Record<string, { id: number }>
|
|
267
213
|
> = {
|
|
268
214
|
process: <K extends keyof Record<string, { id: number }>>(
|
|
269
|
-
signal: Signal<Record<string, { id: number }>[K]>,
|
|
215
|
+
signal: Signal<Record<string, { id: number }>[K] & {}>,
|
|
270
216
|
) => {
|
|
271
217
|
// Process the signal
|
|
272
218
|
const value = signal.get()
|
|
@@ -275,7 +221,8 @@ describe('Type precision tests', () => {
|
|
|
275
221
|
}
|
|
276
222
|
|
|
277
223
|
// This call should work without type errors
|
|
278
|
-
|
|
224
|
+
const item = result.at(0)
|
|
225
|
+
if (item) processor.process(item)
|
|
279
226
|
})
|
|
280
227
|
|
|
281
228
|
test('verifies fixed type inference for external library compatibility', () => {
|
|
@@ -283,23 +230,26 @@ describe('Type precision tests', () => {
|
|
|
283
230
|
{ id: 1, name: 'Alice' },
|
|
284
231
|
{ id: 2, name: 'Bob' },
|
|
285
232
|
]
|
|
286
|
-
const signal =
|
|
287
|
-
const firstItemSignal = signal
|
|
288
|
-
const secondItemSignal = signal
|
|
233
|
+
const signal = createSignal(items)
|
|
234
|
+
const firstItemSignal = signal.at(0)
|
|
235
|
+
const secondItemSignal = signal.at(1)
|
|
289
236
|
|
|
290
237
|
// Runtime behavior works correctly
|
|
291
|
-
expect(
|
|
292
|
-
expect(firstItemSignal
|
|
293
|
-
expect(secondItemSignal
|
|
238
|
+
expect(isList(signal)).toBe(true)
|
|
239
|
+
expect(firstItemSignal?.get()).toEqual({ id: 1, name: 'Alice' })
|
|
240
|
+
expect(secondItemSignal?.get()).toEqual({ id: 2, name: 'Bob' })
|
|
294
241
|
|
|
295
242
|
// Type inference should now work correctly:
|
|
296
|
-
const properlyTyped:
|
|
243
|
+
const properlyTyped: List<{ id: number; name: string }> = signal
|
|
297
244
|
expect(properlyTyped).toBeDefined()
|
|
298
245
|
|
|
299
246
|
// These should work without type errors in external libraries
|
|
300
247
|
// that expect Signal<P[K]> where P[K] is the individual element type
|
|
301
|
-
interface ExternalAPI<P extends
|
|
302
|
-
process<K extends keyof P>(
|
|
248
|
+
interface ExternalAPI<P extends Record<string, object>> {
|
|
249
|
+
process<K extends keyof P>(
|
|
250
|
+
key: K,
|
|
251
|
+
signal: Signal<P[K] & object>,
|
|
252
|
+
): P[K]
|
|
303
253
|
}
|
|
304
254
|
|
|
305
255
|
const api: ExternalAPI<
|
|
@@ -309,15 +259,16 @@ describe('Type precision tests', () => {
|
|
|
309
259
|
}
|
|
310
260
|
|
|
311
261
|
// These calls should work with proper typing now
|
|
312
|
-
const result1 = api.process('0', firstItemSignal)
|
|
313
|
-
const result2 =
|
|
262
|
+
const result1 = firstItemSignal && api.process('0', firstItemSignal)
|
|
263
|
+
const result2 =
|
|
264
|
+
secondItemSignal && api.process('1', secondItemSignal)
|
|
314
265
|
|
|
315
266
|
expect(result1).toEqual({ id: 1, name: 'Alice' })
|
|
316
267
|
expect(result2).toEqual({ id: 2, name: 'Bob' })
|
|
317
268
|
|
|
318
269
|
// Verify the types are precise
|
|
319
|
-
expect(typeof result1
|
|
320
|
-
expect(typeof result1
|
|
270
|
+
expect(typeof result1?.id).toBe('number')
|
|
271
|
+
expect(typeof result1?.name).toBe('string')
|
|
321
272
|
})
|
|
322
273
|
})
|
|
323
274
|
})
|
package/test/state.test.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import {
|
|
2
|
+
import { isComputed, isState, State } from '../index.ts'
|
|
3
3
|
|
|
4
4
|
/* === Tests === */
|
|
5
5
|
|
|
6
6
|
describe('State', () => {
|
|
7
7
|
describe('State type guard', () => {
|
|
8
8
|
test('isState identifies state signals', () => {
|
|
9
|
-
const count =
|
|
9
|
+
const count = new State(42)
|
|
10
10
|
expect(isState(count)).toBe(true)
|
|
11
11
|
expect(isComputed(count)).toBe(false)
|
|
12
12
|
})
|
|
@@ -14,28 +14,28 @@ describe('State', () => {
|
|
|
14
14
|
|
|
15
15
|
describe('Boolean cause', () => {
|
|
16
16
|
test('should be boolean', () => {
|
|
17
|
-
const cause =
|
|
17
|
+
const cause = new State(false)
|
|
18
18
|
expect(typeof cause.get()).toBe('boolean')
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
test('should set initial value to false', () => {
|
|
22
|
-
const cause =
|
|
22
|
+
const cause = new State(false)
|
|
23
23
|
expect(cause.get()).toBe(false)
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
test('should set initial value to true', () => {
|
|
27
|
-
const cause =
|
|
27
|
+
const cause = new State(true)
|
|
28
28
|
expect(cause.get()).toBe(true)
|
|
29
29
|
})
|
|
30
30
|
|
|
31
31
|
test('should set new value with .set(true)', () => {
|
|
32
|
-
const cause =
|
|
32
|
+
const cause = new State(false)
|
|
33
33
|
cause.set(true)
|
|
34
34
|
expect(cause.get()).toBe(true)
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
test('should toggle initial value with .set(v => !v)', () => {
|
|
38
|
-
const cause =
|
|
38
|
+
const cause = new State(false)
|
|
39
39
|
cause.update(v => !v)
|
|
40
40
|
expect(cause.get()).toBe(true)
|
|
41
41
|
})
|
|
@@ -43,23 +43,23 @@ describe('State', () => {
|
|
|
43
43
|
|
|
44
44
|
describe('Number cause', () => {
|
|
45
45
|
test('should be number', () => {
|
|
46
|
-
const cause =
|
|
46
|
+
const cause = new State(0)
|
|
47
47
|
expect(typeof cause.get()).toBe('number')
|
|
48
48
|
})
|
|
49
49
|
|
|
50
50
|
test('should set initial value to 0', () => {
|
|
51
|
-
const cause =
|
|
51
|
+
const cause = new State(0)
|
|
52
52
|
expect(cause.get()).toBe(0)
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
test('should set new value with .set(42)', () => {
|
|
56
|
-
const cause =
|
|
56
|
+
const cause = new State(0)
|
|
57
57
|
cause.set(42)
|
|
58
58
|
expect(cause.get()).toBe(42)
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
test('should increment value with .set(v => ++v)', () => {
|
|
62
|
-
const cause =
|
|
62
|
+
const cause = new State(0)
|
|
63
63
|
cause.update(v => ++v)
|
|
64
64
|
expect(cause.get()).toBe(1)
|
|
65
65
|
})
|
|
@@ -67,23 +67,23 @@ describe('State', () => {
|
|
|
67
67
|
|
|
68
68
|
describe('String cause', () => {
|
|
69
69
|
test('should be string', () => {
|
|
70
|
-
const cause =
|
|
70
|
+
const cause = new State('foo')
|
|
71
71
|
expect(typeof cause.get()).toBe('string')
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
test('should set initial value to "foo"', () => {
|
|
75
|
-
const cause =
|
|
75
|
+
const cause = new State('foo')
|
|
76
76
|
expect(cause.get()).toBe('foo')
|
|
77
77
|
})
|
|
78
78
|
|
|
79
79
|
test('should set new value with .set("bar")', () => {
|
|
80
|
-
const cause =
|
|
80
|
+
const cause = new State('foo')
|
|
81
81
|
cause.set('bar')
|
|
82
82
|
expect(cause.get()).toBe('bar')
|
|
83
83
|
})
|
|
84
84
|
|
|
85
85
|
test('should upper case value with .set(v => v.toUpperCase())', () => {
|
|
86
|
-
const cause =
|
|
86
|
+
const cause = new State('foo')
|
|
87
87
|
cause.update(v => (v ? v.toUpperCase() : ''))
|
|
88
88
|
expect(cause.get()).toBe('FOO')
|
|
89
89
|
})
|
|
@@ -91,31 +91,31 @@ describe('State', () => {
|
|
|
91
91
|
|
|
92
92
|
describe('Array cause', () => {
|
|
93
93
|
test('should be array', () => {
|
|
94
|
-
const cause =
|
|
94
|
+
const cause = new State([1, 2, 3])
|
|
95
95
|
expect(Array.isArray(cause.get())).toBe(true)
|
|
96
96
|
})
|
|
97
97
|
|
|
98
98
|
test('should set initial value to [1, 2, 3]', () => {
|
|
99
|
-
const cause =
|
|
99
|
+
const cause = new State([1, 2, 3])
|
|
100
100
|
expect(cause.get()).toEqual([1, 2, 3])
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
test('should set new value with .set([4, 5, 6])', () => {
|
|
104
|
-
const cause =
|
|
104
|
+
const cause = new State([1, 2, 3])
|
|
105
105
|
cause.set([4, 5, 6])
|
|
106
106
|
expect(cause.get()).toEqual([4, 5, 6])
|
|
107
107
|
})
|
|
108
108
|
|
|
109
109
|
test('should reflect current value of array after modification', () => {
|
|
110
110
|
const array = [1, 2, 3]
|
|
111
|
-
const cause =
|
|
111
|
+
const cause = new State(array)
|
|
112
112
|
array.push(4) // don't do this! the result will be correct, but we can't trigger effects
|
|
113
113
|
expect(cause.get()).toEqual([1, 2, 3, 4])
|
|
114
114
|
})
|
|
115
115
|
|
|
116
116
|
test('should set new value with .set([...array, 4])', () => {
|
|
117
117
|
const array = [1, 2, 3]
|
|
118
|
-
const cause =
|
|
118
|
+
const cause = new State(array)
|
|
119
119
|
cause.set([...array, 4]) // use destructuring instead!
|
|
120
120
|
expect(cause.get()).toEqual([1, 2, 3, 4])
|
|
121
121
|
})
|
|
@@ -124,17 +124,17 @@ describe('State', () => {
|
|
|
124
124
|
test('should throw NullishSignalValueError when initialValue is nullish', () => {
|
|
125
125
|
expect(() => {
|
|
126
126
|
// @ts-expect-error - Testing invalid input
|
|
127
|
-
|
|
127
|
+
new State(null)
|
|
128
128
|
}).toThrow('Nullish signal values are not allowed in state')
|
|
129
129
|
|
|
130
130
|
expect(() => {
|
|
131
131
|
// @ts-expect-error - Testing invalid input
|
|
132
|
-
|
|
132
|
+
new State(undefined)
|
|
133
133
|
}).toThrow('Nullish signal values are not allowed in state')
|
|
134
134
|
})
|
|
135
135
|
|
|
136
136
|
test('should throw NullishSignalValueError when newValue is nullish in set()', () => {
|
|
137
|
-
const state =
|
|
137
|
+
const state = new State(42)
|
|
138
138
|
|
|
139
139
|
expect(() => {
|
|
140
140
|
// @ts-expect-error - Testing invalid input
|
|
@@ -150,7 +150,7 @@ describe('State', () => {
|
|
|
150
150
|
test('should throw specific error types for nullish values', () => {
|
|
151
151
|
try {
|
|
152
152
|
// @ts-expect-error - Testing invalid input
|
|
153
|
-
|
|
153
|
+
new State(null)
|
|
154
154
|
expect(true).toBe(false) // Should not reach here
|
|
155
155
|
} catch (error) {
|
|
156
156
|
expect(error).toBeInstanceOf(TypeError)
|
|
@@ -160,7 +160,7 @@ describe('State', () => {
|
|
|
160
160
|
)
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
const state =
|
|
163
|
+
const state = new State(42)
|
|
164
164
|
try {
|
|
165
165
|
// @ts-expect-error - Testing invalid input
|
|
166
166
|
state.set(null)
|
|
@@ -177,26 +177,26 @@ describe('State', () => {
|
|
|
177
177
|
test('should allow valid non-nullish values', () => {
|
|
178
178
|
// These should not throw
|
|
179
179
|
expect(() => {
|
|
180
|
-
|
|
180
|
+
new State(0)
|
|
181
181
|
}).not.toThrow()
|
|
182
182
|
|
|
183
183
|
expect(() => {
|
|
184
|
-
|
|
184
|
+
new State('')
|
|
185
185
|
}).not.toThrow()
|
|
186
186
|
|
|
187
187
|
expect(() => {
|
|
188
|
-
|
|
188
|
+
new State(false)
|
|
189
189
|
}).not.toThrow()
|
|
190
190
|
|
|
191
191
|
expect(() => {
|
|
192
|
-
|
|
192
|
+
new State({})
|
|
193
193
|
}).not.toThrow()
|
|
194
194
|
|
|
195
195
|
expect(() => {
|
|
196
|
-
|
|
196
|
+
new State([])
|
|
197
197
|
}).not.toThrow()
|
|
198
198
|
|
|
199
|
-
const state =
|
|
199
|
+
const state = new State(42)
|
|
200
200
|
expect(() => {
|
|
201
201
|
state.set(0)
|
|
202
202
|
}).not.toThrow()
|
|
@@ -208,7 +208,7 @@ describe('State', () => {
|
|
|
208
208
|
})
|
|
209
209
|
|
|
210
210
|
test('should throw InvalidCallbackError for non-function updater in update()', () => {
|
|
211
|
-
const state =
|
|
211
|
+
const state = new State(42)
|
|
212
212
|
|
|
213
213
|
expect(() => {
|
|
214
214
|
// @ts-expect-error - Testing invalid input
|
|
@@ -232,7 +232,7 @@ describe('State', () => {
|
|
|
232
232
|
})
|
|
233
233
|
|
|
234
234
|
test('should throw specific error type for non-function updater', () => {
|
|
235
|
-
const state =
|
|
235
|
+
const state = new State(42)
|
|
236
236
|
|
|
237
237
|
try {
|
|
238
238
|
// @ts-expect-error - Testing invalid input
|
|
@@ -248,7 +248,7 @@ describe('State', () => {
|
|
|
248
248
|
})
|
|
249
249
|
|
|
250
250
|
test('should handle updater function that throws an error', () => {
|
|
251
|
-
const state =
|
|
251
|
+
const state = new State(42)
|
|
252
252
|
|
|
253
253
|
expect(() => {
|
|
254
254
|
state.update(() => {
|
|
@@ -261,7 +261,7 @@ describe('State', () => {
|
|
|
261
261
|
})
|
|
262
262
|
|
|
263
263
|
test('should handle updater function that returns nullish value', () => {
|
|
264
|
-
const state =
|
|
264
|
+
const state = new State(42)
|
|
265
265
|
|
|
266
266
|
expect(() => {
|
|
267
267
|
// @ts-expect-error - Testing invalid return value
|
|
@@ -278,25 +278,25 @@ describe('State', () => {
|
|
|
278
278
|
})
|
|
279
279
|
|
|
280
280
|
test('should handle valid updater functions', () => {
|
|
281
|
-
const numberState =
|
|
281
|
+
const numberState = new State(10)
|
|
282
282
|
expect(() => {
|
|
283
283
|
numberState.update(x => x + 5)
|
|
284
284
|
}).not.toThrow()
|
|
285
285
|
expect(numberState.get()).toBe(15)
|
|
286
286
|
|
|
287
|
-
const stringState =
|
|
287
|
+
const stringState = new State('hello')
|
|
288
288
|
expect(() => {
|
|
289
289
|
stringState.update(x => x.toUpperCase())
|
|
290
290
|
}).not.toThrow()
|
|
291
291
|
expect(stringState.get()).toBe('HELLO')
|
|
292
292
|
|
|
293
|
-
const arrayState =
|
|
293
|
+
const arrayState = new State([1, 2, 3])
|
|
294
294
|
expect(() => {
|
|
295
295
|
arrayState.update(arr => [...arr, 4])
|
|
296
296
|
}).not.toThrow()
|
|
297
297
|
expect(arrayState.get()).toEqual([1, 2, 3, 4])
|
|
298
298
|
|
|
299
|
-
const objectState =
|
|
299
|
+
const objectState = new State({ count: 0 })
|
|
300
300
|
expect(() => {
|
|
301
301
|
objectState.update(obj => ({
|
|
302
302
|
...obj,
|
|
@@ -310,24 +310,24 @@ describe('State', () => {
|
|
|
310
310
|
|
|
311
311
|
describe('Object cause', () => {
|
|
312
312
|
test('should be object', () => {
|
|
313
|
-
const cause =
|
|
313
|
+
const cause = new State({ a: 'a', b: 1 })
|
|
314
314
|
expect(typeof cause.get()).toBe('object')
|
|
315
315
|
})
|
|
316
316
|
|
|
317
317
|
test('should set initial value to { a: "a", b: 1 }', () => {
|
|
318
|
-
const cause =
|
|
318
|
+
const cause = new State({ a: 'a', b: 1 })
|
|
319
319
|
expect(cause.get()).toEqual({ a: 'a', b: 1 })
|
|
320
320
|
})
|
|
321
321
|
|
|
322
322
|
test('should set new value with .set({ c: true })', () => {
|
|
323
|
-
const cause =
|
|
323
|
+
const cause = new State<Record<string, unknown>>({ a: 'a', b: 1 })
|
|
324
324
|
cause.set({ c: true })
|
|
325
325
|
expect(cause.get()).toEqual({ c: true })
|
|
326
326
|
})
|
|
327
327
|
|
|
328
328
|
test('should reflect current value of object after modification', () => {
|
|
329
329
|
const obj = { a: 'a', b: 1 }
|
|
330
|
-
const cause =
|
|
330
|
+
const cause = new State<Record<string, unknown>>(obj)
|
|
331
331
|
// @ts-expect-error Property 'c' does not exist on type '{ a: string; b: number; }'. (ts 2339)
|
|
332
332
|
obj.c = true // don't do this! the result will be correct, but we can't trigger effects
|
|
333
333
|
expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
|
|
@@ -335,7 +335,7 @@ describe('State', () => {
|
|
|
335
335
|
|
|
336
336
|
test('should set new value with .set({...obj, c: true})', () => {
|
|
337
337
|
const obj = { a: 'a', b: 1 }
|
|
338
|
-
const cause =
|
|
338
|
+
const cause = new State<Record<string, unknown>>(obj)
|
|
339
339
|
cause.set({ ...obj, c: true }) // use destructuring instead!
|
|
340
340
|
expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
|
|
341
341
|
})
|