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/package.json +3 -4
- package/r&d.md +24 -24
- package/readme.md +5 -87
- package/sprae.js +216 -566
- package/sprae.min.js +1 -1
- package/src/core.js +12 -6
- package/src/directives.js +8 -5
- package/src/state.js +113 -0
- package/test/dom.js +935 -0
- package/test/index.html +2 -1
- package/test/state.js +274 -0
- package/test/test.js +2 -922
- package/todo.md +4 -4
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
|
+
})
|