sprae 2.14.2 → 3.0.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.
package/test/index.html CHANGED
@@ -21,7 +21,8 @@
21
21
  "preact": "../node_modules/preact/dist/preact.mjs",
22
22
  "preact/hooks": "../node_modules/preact/hooks/dist/hooks.mjs",
23
23
  "@preact/signals-core": "../node_modules/@preact/signals-core/dist/signals-core.mjs",
24
- "usignal/sync": "../node_modules/usignal/esm/sync.js"
24
+ "usignal/sync": "../node_modules/usignal/esm/sync.js",
25
+ "staet": "../node_modules/staet/staet.js"
25
26
  }
26
27
  }
27
28
  </script>
package/test/state.js ADDED
@@ -0,0 +1,274 @@
1
+ import {state, fx, batch} from '../src/state.js'
2
+ import t, {is, ok} from 'tst'
3
+ import signalStruct from 'signal-struct'
4
+ import { effect } from '@preact/signals-core'
5
+
6
+ t('state: basic', t => {
7
+ let s = state({x:0, y:1})
8
+
9
+ let xy; fx(() => xy = s.x + s.y)
10
+ is(xy, 1, 'Eq')
11
+ console.log('set 2')
12
+ s.x = 2
13
+ console.log('set 3')
14
+ s.y = 3
15
+ is(xy, 5, 'Eq')
16
+ s.y = 4
17
+ is(xy, 6, 'Eq')
18
+ })
19
+
20
+ t('state: signal-struct basics', t => {
21
+ let s = state({
22
+ x: 0,
23
+ y: 1,
24
+ z: { r: 2, i: 3 },
25
+ v: function(){return 1},
26
+ w: [1,2],
27
+ get xy () { return this.x + this.y },
28
+ set xy ([x,y]) { return this.x = x, this.y = y }
29
+ })
30
+
31
+ // functions are signals too
32
+ is(s.v(), 1)
33
+ // subscribes to only x and y without need for .value access
34
+ const zilog = []
35
+ fx(() => zilog.push(s.z.i))
36
+ is(zilog, [3])
37
+
38
+ let xy; fx(() => xy = s.x + s.y)
39
+ is(xy, 1)
40
+ s.x = 2
41
+ s.y = 3
42
+ is(xy, 5)
43
+ s.y = 4
44
+ is(xy, 6)
45
+
46
+ // getters are computed
47
+ is(s.xy, 6)
48
+ s.xy = [4,2]
49
+ is(s.x, 4)
50
+ is(s.y, 2)
51
+ is(s.xy, 6)
52
+ })
53
+
54
+ t('state: deep props', () => {
55
+ let s = state({
56
+ z: { r: 2, i: 3 }
57
+ })
58
+
59
+ // subscribes to deep values too: only z.r and z.i update result
60
+ let len; fx(() => (len = (s.z.r**2 + s.z.i**2)**0.5))
61
+ s.z.r = 3
62
+ s.z.i = 4
63
+ is(len, 5)
64
+ s.z.r = 4
65
+ s.z.i = 3
66
+ is(len, 5)
67
+
68
+ // updating internal objects/arrays turns them into signals too
69
+ s.z = { r: 5, i: 12}
70
+ is(len, 13)
71
+ })
72
+
73
+ t('state: array', () => {
74
+ let s = state({
75
+ w: [1,2]
76
+ })
77
+
78
+ // updating array is fine
79
+ let mult; fx(() => mult = s.w?.[0] * s.w?.[1] || 0)
80
+ is(mult, 2)
81
+ s.w = [3,4]
82
+ is(mult,12)
83
+
84
+ // nullifying is fine
85
+ s.w = null
86
+ is(mult, 0)
87
+
88
+ // delete is fine
89
+ delete s.w
90
+
91
+ is(mult, 0)
92
+
93
+ console.log('set w')
94
+ s.w = [1,2]
95
+ is(mult, 2)
96
+ })
97
+
98
+ t('state: bulk-update', () => {
99
+ let s = state({
100
+ x: 0,
101
+ y: 1,
102
+ z: { r: 2, i: 3 },
103
+ v: function(){return 1},
104
+ w: [1,2],
105
+ get xy () { return this.x + this.y },
106
+ set xy ([x,y]) { return this.x = x, this.y = y }
107
+ })
108
+
109
+ // FIXME: number of invocations must be minimized
110
+ let xy; fx(() => xy = s.x + s.y)
111
+ let len; fx(() => (len = (s.z.r**2 + s.z.i**2)**0.5))
112
+
113
+ // bulk-update is deep
114
+ // let [signals, update] = s
115
+ // update({ x: 1, y: 1, z: { r: 3, i: 4 } })
116
+ Object.assign(s, { x: 1, y: 1, z: { r: 3, i: 4 } })
117
+ is(xy, 2)
118
+ is(len, 5, 'len after update')
119
+ })
120
+
121
+ t('state: type consistency', () => {
122
+ let s = state({})
123
+
124
+ // signals retain same type as init data
125
+ is(s.constructor, Object)
126
+
127
+ // re-initializing returns itself
128
+ let s1 = state(s)
129
+ is(s, s1)
130
+ })
131
+
132
+ t('state: iteration is safe', () => {
133
+ // it is not enumerable
134
+ let s2 = state([])
135
+ let log = []
136
+ for (let i of s2) log.push(i)
137
+ is(log, [], 'doesn\'t iterate')
138
+ })
139
+
140
+ t('state: state from same instance', () => {
141
+ // NOTE: I guess we may wan to have multiple proxies for same target, don't we?
142
+ let s = {x:1}
143
+ let s1 = state(s)
144
+ let s2 = state(s)
145
+ ok(s1 === s2)
146
+ })
147
+
148
+ t('state: state from state', () => {
149
+ let s = {x:1}
150
+ let s1 = state(s)
151
+ let s2 = state(s1)
152
+ ok(s1 === s2)
153
+ })
154
+
155
+ t('state: inheritance', () => {
156
+ let s = state({ x: 0 })
157
+ let s1 = state({ y: 2 }, s)
158
+ is(s1.x, 0)
159
+ is(s1.y, 2)
160
+
161
+ // descendants are detected as instances
162
+ // let s3 = Object.create(s1), s3s = state(s3)
163
+ // is(s3, s3s)
164
+
165
+ // can subscribe to reactive sources too
166
+ // let s4 = state({
167
+ // p: new Promise(ok => setTimeout(() => ok(123)))
168
+ // })
169
+ // is(s4.p, undefined)
170
+ // setTimeout(() => {
171
+ // is(s4.p, 123)
172
+ // })
173
+
174
+ // let s43 = Object.create(s4, s3)
175
+ // setTimeout(() => {
176
+ // is(s43.p,123)
177
+ // is(s43.y,1)
178
+ // })
179
+ })
180
+
181
+ t('state: inheritance: updating values in chain', () => {
182
+ let s1 = {x:1}
183
+ let s = state(s1, {y:2})
184
+ console.group('fx')
185
+ let xy = 0; fx(() => xy = s.x + s.y);
186
+ console.groupEnd('fx')
187
+ is(xy, 3)
188
+ s.x++
189
+ is(xy, 4)
190
+ console.log('y++')
191
+ s.y++
192
+ is(xy, 5)
193
+ })
194
+
195
+ t('state: array items', () => {
196
+ // arrays get each item converted to signal struct
197
+ let s5 = state({list: [{x:1}, {x:2}]})
198
+ let sum; fx(()=> sum = s5.list.reduce((sum, item)=>item.x + sum, 0))
199
+ is(sum, 3)
200
+ s5.list[0].x = 2
201
+ is(sum, 4)
202
+ console.log('set array value')
203
+ s5.list = [{x:3}, {x:3}]
204
+ is(sum, 6)
205
+ s5.list = [{x:3}, {x:3}, {x:4}]
206
+ is(sum, 10)
207
+ })
208
+
209
+ t('state: arrays retain reference', () => {
210
+ // arrays retain reference
211
+ let list = [1,2,3]
212
+ let s6 = state({list})
213
+ s6.list[1] = 4
214
+ is(list,[1,4,3])
215
+ })
216
+
217
+ t('state: direct list', () => {
218
+ // works with arrays as well
219
+ let list = state([{x:1}, {x:2}])
220
+ let sum; fx(()=> sum = list.reduce((sum, item)=>item.x + sum, 0))
221
+ is(sum, 3)
222
+ list[0].x = 2
223
+ is(sum, 4)
224
+ list.splice(0, 2, {x:3}, {x:3})
225
+ console.log(list)
226
+ is(sum, 6)
227
+ })
228
+
229
+ t('state: array methods', () => {
230
+ // FIXME: somehow sprae was falling with something like this
231
+ let a = state({a:[1]})
232
+ let b = a['a']['map'](x => x * 2)
233
+ is(b, [2])
234
+ })
235
+
236
+ t('state: circular?', () => {
237
+ let a = state([])
238
+ fx(() => a.push(1))
239
+ a.push(2)
240
+ is (a, [1, 2])
241
+ })
242
+
243
+ t('state: batch', () => {
244
+ let s = state({x:1, y:2})
245
+ let log = []; fx(() => log.push(s.x + s.y))
246
+ is(log, [3])
247
+ batch(() => (s.x++, s.y++))
248
+ is(log, [3, 5])
249
+ batch(() => (
250
+ batch(() => s.x++)
251
+ ))
252
+ is(log, [3, 5, 6])
253
+ })
254
+
255
+ t('state: bench', () => {
256
+ const N = 100000
257
+
258
+ let s2 = signalStruct({x:1, y:2}), xy2
259
+ effect(() => xy2 = s2.x * s2.y)
260
+ console.time('signalStruct')
261
+ for (let i = 0; i < N; i++) {
262
+ s2.x++, s2.y++
263
+ }
264
+ console.timeEnd('signalStruct')
265
+
266
+
267
+ let s1 = state({x:1, y:2}), xy
268
+ fx(() => xy = s1.x * s1.y)
269
+ console.time('fxs')
270
+ for (let i = 0; i < N; i++) {
271
+ s1.x++, s1.y++
272
+ }
273
+ console.timeEnd('fxs')
274
+ })