sprae 6.1.0 → 6.1.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.
package/test/events.js DELETED
@@ -1,138 +0,0 @@
1
- // import { signal } from 'usignal/sync'
2
- import test, {is, any, throws} from 'tst'
3
- import {tick, time} from 'wait-please'
4
- import sprae from '../src/index.js'
5
- import h from 'hyperf'
6
-
7
-
8
- test('events: this context', e => {
9
- let el = h`<div @x="log.push(this)"></div>`
10
- let state = sprae(el, {log: []})
11
- el.dispatchEvent(new window.Event('x'));
12
- is(state.log, [el])
13
- })
14
-
15
- test('events: multiple events', e => {
16
- let el = h`<div @scroll@click@x="log.push(event.type)"></div>`
17
- let state = sprae(el, {log:[]})
18
-
19
- el.dispatchEvent(new window.Event('click'));
20
- is(state.log, ['click'])
21
- el.dispatchEvent(new window.Event('scroll'));
22
- is(state.log, ['click','scroll'])
23
- el.dispatchEvent(new window.Event('x'));
24
- is(state.log, ['click','scroll','x'])
25
- })
26
-
27
- test('events: once', e => {
28
- // NOTE: if callback updates it's still rebound
29
- let el = h`<x @x.once="(x&&log.push(this))" ></x>`
30
- let log = []
31
- let state = sprae(el, {log, x:1})
32
- el.dispatchEvent(new window.Event('x'));
33
- is(log, [el])
34
- el.dispatchEvent(new window.Event('x'));
35
- is(log, [el])
36
- state.x = 2
37
- el.dispatchEvent(new window.Event('x'));
38
- el.dispatchEvent(new window.Event('x'));
39
- is(log, [el])
40
- })
41
-
42
- test('events: capture, stop, prevent', e => {
43
- let el = h`<x @x.capture="log.push(1)"><y @x="log.push(2)"></y></x>`
44
- let state = sprae(el, {log:[]})
45
- el.firstChild.dispatchEvent(new window.Event('x', {bubbles:true}));
46
- is(state.log, [1,2])
47
-
48
- let el2 = h`<x @x="log.push(1)"><y @x.stop="log.push(2)"></y></x>`
49
- let state2 = sprae(el2, {log:[]})
50
- el2.firstChild.dispatchEvent(new window.Event('x', {bubbles:true}));
51
- is(state2.log, [2])
52
- })
53
-
54
- test('events: window, self', e => {
55
- let el = h`<x @x.self="log.push(1)"><y @x.window="log.push(2)"></y></x>`
56
- let state = sprae(el, {log:[]})
57
- el.firstChild.dispatchEvent(new window.Event('x', {bubbles:true}));
58
- is(state.log, [])
59
- el.dispatchEvent(new window.Event('x', {bubbles:true}));
60
- is(state.log, [1])
61
- window.dispatchEvent(new window.Event('x', {bubbles:true}));
62
- is(state.log, [1,2])
63
- })
64
-
65
- test('events: keys', e => {
66
- let el = h`<x @keydown.enter="log.push(1)"></x>`
67
- let state = {log:[]}
68
- sprae(el, state)
69
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: '' }));
70
- is(state.log,[])
71
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
72
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: '' }));
73
- is(state.log,[1])
74
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
75
- is(state.log,[1,1])
76
- })
77
-
78
- test('events: key combinations', e => {
79
- let el = h`<x @keydown.ctrl-enter="log.push(1)"></x>`
80
- let state = {log:[]}
81
- sprae(el, state)
82
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: '' }));
83
- is(state.log,[])
84
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
85
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: '' }));
86
- is(state.log,[])
87
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true }));
88
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
89
- is(state.log,[1])
90
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
91
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true }));
92
- is(state.log,[1,1])
93
- let el2 = h`<x @keydown.ctrl-alt-enter="log.push(1)"></x>`
94
- })
95
-
96
- test('events: keys with prevent', e => {
97
- let el = h`<y @keydown="log.push(event.key)"><x :ref="x" @keydown.enter.stop></x></y>`
98
- let state = sprae(el, {log:[]})
99
- state.x.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
100
- console.log('enter')
101
- state.x.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
102
- is(state.log,['x'])
103
- })
104
-
105
- test('events: debounce', async e => {
106
- let el = h`<x @keydown.debounce-1="log.push(event.key)"></x>`
107
- let state = sprae(el, {log:[]})
108
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
109
- is(state.log, [])
110
- await time(2)
111
- is(state.log, ['x'])
112
- })
113
-
114
- test('events: debounce 0', async e => {
115
- let el = h`<x @keydown.debounce-0="log.push(event.key)"></x>`
116
- let state = sprae(el, {log:[]})
117
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
118
- is(state.log, [])
119
- await time(2)
120
- is(state.log, ['x'])
121
- })
122
-
123
- test('events: throttle', async e => {
124
- let el = h`<x @keydown.throttle-10="log.push(event.key)"></x>`
125
- let state = sprae(el, {log:[]})
126
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
127
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
128
- is(state.log, ['x'])
129
- await time(5)
130
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
131
- is(state.log, ['x'])
132
- await time(10)
133
- is(state.log, ['x', 'x'])
134
- await time(10)
135
- is(state.log, ['x', 'x'])
136
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
137
- is(state.log, ['x', 'x', 'x'])
138
- })
package/test/index.html DELETED
@@ -1,36 +0,0 @@
1
- <!doctype html>
2
- <meta charset=utf-8>
3
- <title>Test</title>
4
-
5
- <script defer src="../sprae.auto.js"></script>
6
- <div :with="{data:'autoinit works'}">
7
- <span :text="data">autoinit is broken</span>
8
- </div>
9
-
10
- <script async src="../node_modules/es-module-shims/dist/es-module-shims.js"></script>
11
- <script type="importmap">
12
- {
13
- "imports": {
14
- "sprae" : "../src/index.js",
15
- "tst" : "../node_modules/tst/tst.js",
16
- "wait-please" : "../node_modules/wait-please/index.js",
17
- "value-ref" : "../node_modules/value-ref/value-ref.js",
18
- "hyperf" : "../node_modules/hyperf/hyperf.js",
19
- "sube" : "../node_modules/sube/sube.js",
20
- "signal-struct" : "../node_modules/signal-struct/signal-struct.js",
21
- "element-props" : "../node_modules/element-props/element-props.js",
22
- "swapdom" : "../node_modules/swapdom/swap-inflate.js",
23
- "swapdom/swap-deflate" : "../node_modules/swapdom/swap-deflate.js",
24
- "swapdom/swap-inflate" : "../node_modules/swapdom/swap-inflate.js",
25
- "primitive-pool" : "../node_modules/primitive-pool/index.js",
26
- "preact": "../node_modules/preact/dist/preact.mjs",
27
- "preact/hooks": "../node_modules/preact/hooks/dist/hooks.mjs",
28
- "@preact/signals-core": "../node_modules/@preact/signals-core/dist/signals-core.mjs",
29
- "usignal/sync": "../node_modules/usignal/esm/sync.js",
30
- "staet": "../node_modules/staet/staet.js"
31
- }
32
- }
33
- </script>
34
-
35
- <script src="./test.js" type="module"></script>
36
-
package/test/register.cjs DELETED
@@ -1,20 +0,0 @@
1
- // provide DOM env for node tests
2
- let { JSDOM } = require('jsdom')
3
-
4
- const { window } = new JSDOM(`<!DOCTYPE html>`, {
5
- url: "http://localhost/",
6
- storageQuota: 10000000,
7
- pretendToBeVisual: true,
8
- FetchExternalResources: false,
9
- ProcessExternalResources: false
10
- })
11
-
12
- let props = Object.getOwnPropertyNames(window)
13
-
14
- props.forEach(prop => {
15
- if (prop in global) return
16
- Object.defineProperty(global, prop, {
17
- configurable: true,
18
- get: () => window[prop]
19
- })
20
- })
package/test/state.js DELETED
@@ -1,296 +0,0 @@
1
- import {state, fx} 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
- import {tick} from 'wait-please'
6
-
7
- t('state: basic', async t => {
8
- let s = state({x:0, y:1})
9
-
10
- let xy; fx(() => xy = s.x + s.y)
11
- is(xy, 1, 'Eq')
12
- console.log('set 2')
13
- s.x = 2
14
- console.log('set 3')
15
- s.y = 3
16
- await tick()
17
- is(xy, 5, 'Eq')
18
- s.y = 4
19
- await tick()
20
- is(xy, 6, 'Eq')
21
- })
22
-
23
- t('state: signal-struct basics', async t => {
24
- let s = state({
25
- x: 0,
26
- y: 1,
27
- z: { r: 2, i: 3 },
28
- v: function(){return 1},
29
- w: [1,2],
30
- get xy () { return this.x + this.y },
31
- set xy ([x,y]) { return this.x = x, this.y = y }
32
- })
33
-
34
- // functions are signals too
35
- is(s.v(), 1)
36
- // subscribes to only x and y without need for .value access
37
- const zilog = []
38
- fx(() => zilog.push(s.z.i))
39
- is(zilog, [3])
40
-
41
- let xy; fx(() => xy = s.x + s.y)
42
- is(xy, 1)
43
- s.x = 2
44
- s.y = 3
45
- await tick()
46
- is(xy, 5)
47
- s.y = 4
48
- await tick()
49
- is(xy, 6)
50
-
51
- // getters are computed
52
- is(s.xy, 6)
53
- s.xy = [4,2]
54
- is(s.x, 4)
55
- is(s.y, 2)
56
- is(s.xy, 6)
57
- })
58
-
59
- t('state: deep props', async () => {
60
- let s = state({
61
- z: { r: 2, i: 3 }
62
- })
63
-
64
- // subscribes to deep values too: only z.r and z.i update result
65
- let len; fx(() => (len = (s.z.r**2 + s.z.i**2)**0.5))
66
- s.z.r = 3
67
- s.z.i = 4
68
- await tick()
69
- is(len, 5)
70
- s.z.r = 4
71
- s.z.i = 3
72
- await tick()
73
- is(len, 5)
74
-
75
- // updating internal objects/arrays turns them into signals too
76
- s.z = { r: 5, i: 12}
77
- await tick()
78
- is(len, 13)
79
- })
80
-
81
- t('state: array', async () => {
82
- let s = state({
83
- w: [1,2]
84
- })
85
-
86
- // updating array is fine
87
- let mult; fx(() => mult = s.w?.[0] * s.w?.[1] || 0)
88
- is(mult, 2)
89
- s.w = [3,4]
90
- await tick()
91
- is(mult,12)
92
-
93
- // nullifying is fine
94
- s.w = null
95
- await tick()
96
- is(mult, 0)
97
-
98
- // delete is fine
99
- delete s.w
100
-
101
- await tick()
102
- is(mult, 0)
103
-
104
- console.log('set w')
105
- s.w = [1,2]
106
- await tick()
107
- is(mult, 2)
108
- })
109
-
110
- t('state: bulk-update', async () => {
111
- let s = state({
112
- x: 0,
113
- y: 1,
114
- z: { r: 2, i: 3 },
115
- v: function(){return 1},
116
- w: [1,2],
117
- get xy () { return this.x + this.y },
118
- set xy ([x,y]) { return this.x = x, this.y = y }
119
- })
120
-
121
- // FIXME: number of invocations must be minimized
122
- let xy; fx(() => xy = s.x + s.y)
123
- let len; fx(() => (len = (s.z.r**2 + s.z.i**2)**0.5))
124
-
125
- // bulk-update is deep
126
- // let [signals, update] = s
127
- // update({ x: 1, y: 1, z: { r: 3, i: 4 } })
128
- Object.assign(s, { x: 1, y: 1, z: { r: 3, i: 4 } })
129
- await tick()
130
- is(xy, 2)
131
- is(len, 5, 'len after update')
132
- })
133
-
134
- t('state: type consistency', () => {
135
- let s = state({})
136
-
137
- // signals retain same type as init data
138
- is(s.constructor, Object)
139
-
140
- // re-initializing returns itself
141
- let s1 = state(s)
142
- is(s, s1)
143
- })
144
-
145
- t('state: iteration is safe', () => {
146
- // it is not enumerable
147
- let s2 = state([])
148
- let log = []
149
- for (let i of s2) log.push(i)
150
- is(log, [], 'doesn\'t iterate')
151
- })
152
-
153
- t('state: state from same instance', () => {
154
- // NOTE: I guess we may wan to have multiple proxies for same target, don't we?
155
- let s = {x:1}
156
- let s1 = state(s)
157
- let s2 = state(s)
158
- ok(s1 === s2)
159
- })
160
-
161
- t('state: state from state', () => {
162
- let s = {x:1}
163
- let s1 = state(s)
164
- let s2 = state(s1)
165
- ok(s1 === s2)
166
- })
167
-
168
- t('state: inheritance', () => {
169
- let s = state({ x: 0 })
170
- let s1 = state({ y: 2 }, s)
171
- is(s1.x, 0)
172
- is(s1.y, 2)
173
-
174
- // descendants are detected as instances
175
- // let s3 = Object.create(s1), s3s = state(s3)
176
- // is(s3, s3s)
177
-
178
- // can subscribe to reactive sources too
179
- // let s4 = state({
180
- // p: new Promise(ok => setTimeout(() => ok(123)))
181
- // })
182
- // is(s4.p, undefined)
183
- // setTimeout(() => {
184
- // is(s4.p, 123)
185
- // })
186
-
187
- // let s43 = Object.create(s4, s3)
188
- // setTimeout(() => {
189
- // is(s43.p,123)
190
- // is(s43.y,1)
191
- // })
192
- })
193
-
194
- t('state: inheritance: updating values in chain', async () => {
195
- let s1 = {x:1}
196
- let s = state(s1, {y:2})
197
- // console.group('fx')
198
- let xy = 0; fx(() => xy = s.x + s.y);
199
- // console.groupEnd('fx')
200
- await tick()
201
- is(xy, 3)
202
- s.x++
203
- await tick()
204
- is(xy, 4)
205
- console.log('y++')
206
- s.y++
207
- await tick()
208
- is(xy, 5)
209
- })
210
-
211
- t('state: array items', async () => {
212
- // arrays get each item converted to signal struct
213
- let s5 = state({list: [{x:1}, {x:2}]})
214
- let sum; fx(()=> sum = s5.list.reduce((sum, item)=>item.x + sum, 0))
215
- is(sum, 3)
216
- s5.list[0].x = 2
217
- await tick()
218
- is(sum, 4)
219
- console.log('set array value')
220
- s5.list = [{x:3}, {x:3}]
221
- await tick()
222
- is(sum, 6)
223
- s5.list = [{x:3}, {x:3}, {x:4}]
224
- await tick()
225
- is(sum, 10)
226
- })
227
-
228
- t('state: arrays retain reference', () => {
229
- // arrays retain reference
230
- let list = [1,2,3]
231
- let s6 = state({list})
232
- s6.list[1] = 4
233
- is(list,[1,4,3])
234
- })
235
-
236
- t('state: direct list', async () => {
237
- // works with arrays as well
238
- let list = state([{x:1}, {x:2}])
239
- let sum; fx(()=> sum = list.reduce((sum, item)=>item.x + sum, 0))
240
- is(sum, 3)
241
- list[0].x = 2
242
- is(list[0].x, 2)
243
- await tick()
244
- is(sum, 4)
245
- list.splice(0, 2, {x:3}, {x:3})
246
- await tick()
247
- console.log(list)
248
- is(sum, 6)
249
- })
250
-
251
- t('state: array methods', () => {
252
- // FIXME: somehow sprae was falling with something like this
253
- let a = state({a:[1]})
254
- let b = a['a']['map'](x => x * 2)
255
- is(b, [2])
256
- })
257
-
258
- t('state: circular?', () => {
259
- let a = state([])
260
- fx(() => a.push(1))
261
- a.push(2)
262
- is (a, [1, 2])
263
- })
264
-
265
- t.skip('state: batch', () => {
266
- let s = state({x:1, y:2})
267
- let log = []; fx(() => log.push(s.x + s.y))
268
- is(log, [3])
269
- batch(() => (s.x++, s.y++))
270
- is(log, [3, 5])
271
- batch(() => (
272
- batch(() => s.x++)
273
- ))
274
- is(log, [3, 5, 6])
275
- })
276
-
277
- t('state: bench', () => {
278
- const N = 100000
279
-
280
- let s2 = signalStruct({x:1, y:2}), xy2
281
- effect(() => xy2 = s2.x * s2.y)
282
- console.time('signalStruct')
283
- for (let i = 0; i < N; i++) {
284
- s2.x++, s2.y++
285
- }
286
- console.timeEnd('signalStruct')
287
-
288
-
289
- let s1 = state({x:1, y:2}), xy
290
- fx(() => xy = s1.x * s1.y)
291
- console.time('fxs')
292
- for (let i = 0; i < N; i++) {
293
- s1.x++, s1.y++
294
- }
295
- console.timeEnd('fxs')
296
- })
package/test/test.js DELETED
@@ -1,13 +0,0 @@
1
- import './state.js'
2
- import './attrs.js'
3
- import './events.js'
4
-
5
- Object.defineProperty(DocumentFragment.prototype, 'outerHTML', {
6
- get() {
7
- let s = ''
8
- this.childNodes.forEach(n => {
9
- s += n.nodeType === 3 ? n.textContent : n.outerHTML != null ? n.outerHTML : ''
10
- })
11
- return s
12
- }
13
- })
package/todo.md DELETED
@@ -1,54 +0,0 @@
1
- * [x] finish directives
2
- * [x] better list diffing
3
- * [x] ordered directives init (:each + :if vs :if + :each) -> find out if really needed and which is faster
4
- -> yes, needed and solve many init issues.
5
- * [?] autoinit -> too much maintenance burden
6
- * [x] node tests
7
- * [x] better deps updating -> cumulative signal
8
- * [x] combinations: :else :if
9
- * [x] :each :if, :if :each
10
- * [x] :each :each
11
- * [x] :with must be able to write state value as well
12
- * [x] docs: give example to each directive
13
- * [x] initialize per-element: <x :each><y :if></y><x> - tree-dependent (:each comes first).
14
- * [x] generalize common attributes :prop="xyz"
15
- * [x] spread props
16
- * [x] optimization: arrays with multiple elements can be slow on creation. Maybe signal-struct must ignore arrays.
17
- -> yep: arrays are rarely changed as `a[i]=newItem` and regularly they're mapped.
18
- * [x] expand to any subscribables: both as state vars
19
- * [x] :ref
20
- * [x] :ref + :each
21
- * [x] event chains :ona-onb
22
- * [x] bulk events :ona:onb
23
- * [x] multiprop setter :a:b="c"
24
- * [x] make `this` in expression an element
25
- * ~~[x] replace :ref with :with="this as x"~~
26
- * [x] :ref creates instance in current state, not creates a new state
27
- * [x] to avoid extending signal-struct, we must collect state data before, and call updates after for extended state
28
- * [x] optimization: replace element-props with direct (better) setters
29
- * [x] Make sure `false` gets serialized, not removes attr
30
- * [x] Sandbox expressions: no global, no "scope" object name, no "arguments"
31
- * ~~[x] report usignal problem~~ author is not really interested
32
- * [x] `this` doesn't refer to element/scope in event handlers
33
- * [x] :text="" empty values shouldn't throw
34
- * [x] implement :with
35
- * [x] update :value without losing focus / position
36
- * ~~[x] run tiredown if element got removed from condition or loop (free memory)~~ no need just make sure no refs to elements stored
37
- * [x] `sprae(el, newState)` can update element's state directly (as batch!?) -> must be tested against repeats in directives
38
- * [x] :if :ref, :if :with -> context setters must come first always
39
- * [x] :style="{'--x':value}"
40
- * [x] :onkeydown.ctrl-alt-D
41
- * [ ] frameworks benchmark
42
- * [x] examples
43
- * [x] todomvc
44
- * [x] waveplay
45
- * [x] evt modifiers
46
- * [x] once, capture, passive
47
- * [x] ...rest
48
- * [x] parallel chains
49
- * [x] Sandbox
50
- * [x] Autorun
51
- * [x] There's some bug with prostogreen not triggering effect. (caused by special array.length case)
52
- * [x] Getters must become evaluable
53
- * [x] `<li :each="item in items" :with="{collapsed:true}"><button :onclick='e=>collapsed=false'></li>`
54
- * [ ] `:with="{likes:[], like(){ /* likes should not be undefined here */ }}"`