sprae 3.0.1 → 4.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 +5 -3
- package/r&d.md +6 -0
- package/readme.md +55 -82
- package/sprae.auto.js +617 -0
- package/sprae.auto.min.js +1 -0
- package/sprae.js +20 -33
- package/sprae.min.js +1 -1
- package/src/core.js +2 -6
- package/src/directives.js +5 -22
- package/src/index.js +1 -5
- package/src/state.js +13 -10
- package/test/dom.js +78 -22
- package/test/index.html +6 -1
- package/test/state.js +34 -12
- package/todo.md +7 -5
package/test/dom.js
CHANGED
|
@@ -25,6 +25,7 @@ test('hidden: core', async () => {
|
|
|
25
25
|
let params = sprae(el, {hidden:true})
|
|
26
26
|
is(el.outerHTML, `<div hidden=""></div>`)
|
|
27
27
|
params.hidden = false
|
|
28
|
+
await tick()
|
|
28
29
|
is(el.outerHTML, `<div></div>`)
|
|
29
30
|
})
|
|
30
31
|
|
|
@@ -42,6 +43,7 @@ test('common: reactive', async () => {
|
|
|
42
43
|
let params = sprae(el, {name:'text', url:'//google.com'})
|
|
43
44
|
is(el.outerHTML, `<label for="text">text</label><input id="text" name="text" type="text"><a href="//google.com"></a><img src="//google.com">`)
|
|
44
45
|
params.name = 'email'
|
|
46
|
+
await tick()
|
|
45
47
|
is(el.outerHTML, `<label for="email">email</label><input id="email" name="email" type="email"><a href="//google.com"></a><img src="//google.com">`)
|
|
46
48
|
})
|
|
47
49
|
|
|
@@ -76,17 +78,22 @@ test('style', async () => {
|
|
|
76
78
|
let el = h`<x style="left: 1px" :style="style"></x>`
|
|
77
79
|
let params = sprae(el, {style: "top: 1px"})
|
|
78
80
|
is(el.outerHTML, `<x style="left: 1px; top: 1px"></x>`)
|
|
81
|
+
|
|
79
82
|
params.style = {top: '2px'}
|
|
83
|
+
await tick()
|
|
80
84
|
is(el.outerHTML, `<x style="left: 1px; top: 2px;"></x>`)
|
|
81
85
|
|
|
82
86
|
params.style = {'--x': 123}
|
|
87
|
+
await tick()
|
|
83
88
|
is(el.style.getPropertyValue('--x'), '123')
|
|
84
89
|
|
|
85
90
|
params.style = {top:'1px', bottom:'2px'}
|
|
91
|
+
await tick()
|
|
86
92
|
is(el.outerHTML, `<x style="left: 1px; top: 1px; bottom: 2px;"></x>`)
|
|
87
93
|
|
|
88
94
|
params.style = {top:'2px', bottom: null}
|
|
89
95
|
// FIXME
|
|
96
|
+
await tick()
|
|
90
97
|
is(el.outerHTML, `<x style="left: 1px; top: 2px;"></x>`)
|
|
91
98
|
})
|
|
92
99
|
|
|
@@ -96,6 +103,7 @@ test('class', async () => {
|
|
|
96
103
|
let params = sprae(el, {a:'x', b:'y', c, d:false});
|
|
97
104
|
is(el.outerHTML, `<x class="base x"></x><y class="y z"></y><z class="b"></z>`);
|
|
98
105
|
params.d = true;
|
|
106
|
+
await tick()
|
|
99
107
|
is(el.outerHTML, `<x class="base x"></x><y class="y z"></y><z class="b c"></z>`);
|
|
100
108
|
// c.value = 'w'
|
|
101
109
|
// is(el.outerHTML, `<x class="base x"></x><y class="y w"></y><z class="b c"></z>`);
|
|
@@ -126,6 +134,7 @@ test('props: calculation', async () => {
|
|
|
126
134
|
let state = sprae(el, {x:3});
|
|
127
135
|
is(el.outerHTML, `<x x="012"></x>`)
|
|
128
136
|
state.x = 4
|
|
137
|
+
await tick()
|
|
129
138
|
is(el.outerHTML, `<x x="0123"></x>`)
|
|
130
139
|
})
|
|
131
140
|
|
|
@@ -138,13 +147,13 @@ test.todo('props: semicols in expression', async () => {
|
|
|
138
147
|
})
|
|
139
148
|
|
|
140
149
|
|
|
141
|
-
test('data: base', async () => {
|
|
150
|
+
test.skip('data: base', async () => {
|
|
142
151
|
let el = h`<input :data="{a:1, fooBar:2}"/>`
|
|
143
152
|
let params = sprae(el)
|
|
144
153
|
is(el.outerHTML, `<input data-a="1" data-foo-bar="2">`)
|
|
145
154
|
})
|
|
146
155
|
|
|
147
|
-
test('aria: base', async () => {
|
|
156
|
+
test.skip('aria: base', async () => {
|
|
148
157
|
let el = h`<input type="text" id="jokes" role="combobox" :aria="{controls:'joketypes', autocomplete:'list', expanded:false, activeOption:'item1', activedescendant:'', xxx:null}"/>`
|
|
149
158
|
sprae(el)
|
|
150
159
|
is(el.outerHTML, `<input type="text" id="jokes" role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant="">`)
|
|
@@ -156,6 +165,7 @@ test('value: direct', async () => {
|
|
|
156
165
|
is(el.value, '1')
|
|
157
166
|
is(el.outerHTML, `<input value="1">`)
|
|
158
167
|
state.a = 2
|
|
168
|
+
await tick()
|
|
159
169
|
is(el.value, '2')
|
|
160
170
|
is(el.outerHTML, `<input value="2">`)
|
|
161
171
|
|
|
@@ -182,6 +192,7 @@ test('text: core', async () => {
|
|
|
182
192
|
let params = sprae(el, {text:'abc'})
|
|
183
193
|
is(el.outerHTML, `<div>abc</div>`)
|
|
184
194
|
params.text = null
|
|
195
|
+
await tick()
|
|
185
196
|
is(el.outerHTML, `<div></div>`)
|
|
186
197
|
})
|
|
187
198
|
|
|
@@ -196,10 +207,13 @@ test('if: base', async () => {
|
|
|
196
207
|
|
|
197
208
|
is(el.innerHTML, '<span>a</span>')
|
|
198
209
|
params.a = 2
|
|
210
|
+
await tick()
|
|
199
211
|
is(el.innerHTML, '<span>b</span>')
|
|
200
212
|
params.a = 3
|
|
213
|
+
await tick()
|
|
201
214
|
is(el.innerHTML, '<span>c</span>')
|
|
202
215
|
params.a = null
|
|
216
|
+
await tick()
|
|
203
217
|
is(el.innerHTML, '<span>c</span>')
|
|
204
218
|
})
|
|
205
219
|
|
|
@@ -214,15 +228,20 @@ test('if: short with insertions', async () => {
|
|
|
214
228
|
|
|
215
229
|
is(el.innerHTML, '<span>1:1</span>')
|
|
216
230
|
params.a = 2
|
|
231
|
+
await tick()
|
|
217
232
|
is(el.innerHTML, '<span>2:2</span>')
|
|
218
233
|
params.a = 3
|
|
234
|
+
await tick()
|
|
219
235
|
is(el.innerHTML, '<span>3</span>')
|
|
220
236
|
params.a = 4
|
|
237
|
+
await tick()
|
|
221
238
|
is(el.innerHTML, '<span>4</span>')
|
|
222
239
|
|
|
223
240
|
params.a = 1
|
|
241
|
+
await tick()
|
|
224
242
|
is(el.innerHTML, '<span>1:1</span>')
|
|
225
243
|
params.a = 4
|
|
244
|
+
await tick()
|
|
226
245
|
is(el.innerHTML, '<span>4</span>')
|
|
227
246
|
|
|
228
247
|
params.a = null
|
|
@@ -258,15 +277,16 @@ test('if: (#3) subsequent content is not abandoned', async () => {
|
|
|
258
277
|
is(x.outerHTML, `<x><z>123</z></x>`)
|
|
259
278
|
})
|
|
260
279
|
|
|
261
|
-
test('if: + :
|
|
262
|
-
let el = h`<div><x :if="x" :
|
|
280
|
+
test('if: + :scope doesnt prevent secondary effects from happening', async () => {
|
|
281
|
+
let el = h`<div><x :if="x" :scope="{}" :text="x"></x></div>`
|
|
263
282
|
let state = sprae(el, {x:''})
|
|
264
283
|
is(el.innerHTML, ``)
|
|
265
284
|
state.x = '123'
|
|
285
|
+
await tick()
|
|
266
286
|
is(el.innerHTML, `<x>123</x>`)
|
|
267
287
|
|
|
268
288
|
// NOTE: we ignore this case
|
|
269
|
-
// let el2 = h`<div><x :if="x" :
|
|
289
|
+
// let el2 = h`<div><x :if="x" :scope="{x:cond}" :text="x"></x></div>`
|
|
270
290
|
// let state2 = sprae(el, {cond:''})
|
|
271
291
|
// is(el2.innerHTML, ``)
|
|
272
292
|
// state2.cond = '123'
|
|
@@ -284,10 +304,13 @@ test('each: array', async () => {
|
|
|
284
304
|
is(el.innerHTML, '')
|
|
285
305
|
console.log('set 1,2')
|
|
286
306
|
params.b = [1,2]
|
|
307
|
+
await tick()
|
|
287
308
|
is(el.innerHTML, '<span>1</span><span>2</span>')
|
|
288
309
|
params.b = []
|
|
310
|
+
await tick()
|
|
289
311
|
is(el.innerHTML, '')
|
|
290
312
|
params.b = null
|
|
313
|
+
await tick()
|
|
291
314
|
is(el.innerHTML, '')
|
|
292
315
|
})
|
|
293
316
|
|
|
@@ -302,10 +325,13 @@ test('each: object', async () => {
|
|
|
302
325
|
is(el.innerHTML, '')
|
|
303
326
|
console.log('set 1,2')
|
|
304
327
|
params.b = { x:1, y:2 }
|
|
328
|
+
await tick()
|
|
305
329
|
is(el.innerHTML, '<span>x,1</span><span>y,2</span>')
|
|
306
330
|
params.b = []
|
|
331
|
+
await tick()
|
|
307
332
|
is(el.innerHTML, '')
|
|
308
333
|
params.b = null
|
|
334
|
+
await tick()
|
|
309
335
|
is(el.innerHTML, '')
|
|
310
336
|
})
|
|
311
337
|
|
|
@@ -318,12 +344,15 @@ test('each: loop within loop', async () => {
|
|
|
318
344
|
|
|
319
345
|
is(el.innerHTML, '<x><y>1</y><y>2</y></x><x><y>3</y><y>4</y></x>')
|
|
320
346
|
params.c = [[5,6], [3,4]]
|
|
347
|
+
await tick()
|
|
321
348
|
is(el.innerHTML, '<x><y>5</y><y>6</y></x><x><y>3</y><y>4</y></x>')
|
|
322
349
|
// params.c[1] = [7,8]
|
|
323
350
|
params.c = [params.c[0], [7,8]]
|
|
351
|
+
await tick()
|
|
324
352
|
is(el.innerHTML, '<x><y>5</y><y>6</y></x><x><y>7</y><y>8</y></x>')
|
|
325
353
|
// is(el.innerHTML, '<span>1</span><span>2</span>')
|
|
326
354
|
params.c = []
|
|
355
|
+
await tick()
|
|
327
356
|
is(el.innerHTML, '')
|
|
328
357
|
// params.b = null
|
|
329
358
|
// is(el.innerHTML, '')
|
|
@@ -358,10 +387,13 @@ test('each: loop with condition', async () => {
|
|
|
358
387
|
|
|
359
388
|
is(el.innerHTML, '')
|
|
360
389
|
params.c = true
|
|
390
|
+
await tick()
|
|
361
391
|
is(el.innerHTML, '<span>1</span><span>2</span>')
|
|
362
392
|
params.b = [1]
|
|
393
|
+
await tick()
|
|
363
394
|
is(el.innerHTML, '<span>1</span>')
|
|
364
395
|
params.b = null
|
|
396
|
+
await tick()
|
|
365
397
|
is(el.innerHTML, '')
|
|
366
398
|
})
|
|
367
399
|
|
|
@@ -375,13 +407,17 @@ test('each: condition with loop', async () => {
|
|
|
375
407
|
|
|
376
408
|
is(el.innerHTML, '<span>false</span>')
|
|
377
409
|
params.c = true
|
|
410
|
+
await tick()
|
|
378
411
|
is(el.innerHTML, '<span>1</span><span>2</span>')
|
|
379
412
|
params.b = [1]
|
|
413
|
+
await tick()
|
|
380
414
|
is(el.innerHTML, '<span>1</span>')
|
|
381
415
|
params.b = null
|
|
416
|
+
await tick()
|
|
382
417
|
is(el.innerHTML, '')
|
|
383
418
|
console.log('c=false')
|
|
384
419
|
params.c = false
|
|
420
|
+
await tick()
|
|
385
421
|
is(el.innerHTML, '<span>false</span>')
|
|
386
422
|
})
|
|
387
423
|
|
|
@@ -395,8 +431,10 @@ test('each: loop within condition', async () => {
|
|
|
395
431
|
|
|
396
432
|
is(el.innerHTML, '<x><y>1</y></x>')
|
|
397
433
|
params.a = 2
|
|
434
|
+
await tick()
|
|
398
435
|
is(el.innerHTML, '<x><y>-1</y><y>-2</y></x>')
|
|
399
436
|
params.a = 0
|
|
437
|
+
await tick()
|
|
400
438
|
is(el.innerHTML, '')
|
|
401
439
|
})
|
|
402
440
|
|
|
@@ -413,14 +451,16 @@ test('each: condition within loop', async () => {
|
|
|
413
451
|
|
|
414
452
|
is(el.innerHTML, '<x><y>1:1</y></x><x><y>2:2</y></x><x><y>3</y></x>')
|
|
415
453
|
params.b = [2]
|
|
454
|
+
await tick()
|
|
416
455
|
is(el.innerHTML, '<x><y>2:2</y></x>')
|
|
417
456
|
params.b = null
|
|
457
|
+
await tick()
|
|
418
458
|
is(el.innerHTML, '')
|
|
419
459
|
})
|
|
420
460
|
|
|
421
461
|
test('each: next items have own "this", not single one', async () => {
|
|
422
462
|
// FIXME: let el = h`<x :each="x in 3"></x>`
|
|
423
|
-
let el = h`<div><x :each="x in 3" :data="
|
|
463
|
+
let el = h`<div><x :each="x in 3" :data-x="x" :x="log.push(x, this.dataset.x)"></x></div>`
|
|
424
464
|
let log = []
|
|
425
465
|
let state = sprae(el, {log})
|
|
426
466
|
is(state.log, [1,'1',2,'2',3,'3'])
|
|
@@ -433,9 +473,11 @@ test('each: unkeyed', async () => {
|
|
|
433
473
|
is(el.textContent, '123')
|
|
434
474
|
// let first = el.firstChild
|
|
435
475
|
state.xs = [1,3,2]
|
|
476
|
+
await tick()
|
|
436
477
|
// is(el.firstChild, first)
|
|
437
478
|
is(el.textContent, '132')
|
|
438
479
|
state.xs = [3,3,3]
|
|
480
|
+
await tick()
|
|
439
481
|
is(el.textContent, '333')
|
|
440
482
|
// is(el.firstChild, first)
|
|
441
483
|
})
|
|
@@ -448,9 +490,11 @@ test('each: keyed', async () => {
|
|
|
448
490
|
is(el.textContent, '123')
|
|
449
491
|
let first = el.firstChild
|
|
450
492
|
state.xs = [1,3,2]
|
|
493
|
+
await tick()
|
|
451
494
|
is(el.firstChild, first)
|
|
452
495
|
is(el.textContent, '132')
|
|
453
496
|
state.xs = [3,3,3]
|
|
497
|
+
await tick()
|
|
454
498
|
is(el.textContent, '3')
|
|
455
499
|
// is(el.firstChild, first)
|
|
456
500
|
})
|
|
@@ -465,18 +509,21 @@ test('each: unmounted elements remove listeners', async () => {
|
|
|
465
509
|
// let's hope they get removed without memory leaks :')
|
|
466
510
|
})
|
|
467
511
|
|
|
468
|
-
test('each: internal children get updated by state update, also: update by running again', () => {
|
|
512
|
+
test('each: internal children get updated by state update, also: update by running again', async () => {
|
|
469
513
|
let el = h`<><x :each="item, idx in items" :text="item" :key="idx"></x></>`
|
|
470
514
|
let state = sprae(el, { items: [1,2,3] })
|
|
471
515
|
is(el.textContent, '123')
|
|
472
516
|
state.items = [2, 2, 3]
|
|
517
|
+
await tick()
|
|
473
518
|
is(el.textContent, '223')
|
|
474
519
|
state = sprae(el, { items: [0,2,3] })
|
|
520
|
+
await tick()
|
|
475
521
|
is(el.textContent, '023')
|
|
476
522
|
// NOTE: this doesn't update items, since they're new array
|
|
477
523
|
console.log('set items')
|
|
478
524
|
state.items[0] = 1
|
|
479
|
-
state.items = [...state.items]
|
|
525
|
+
// state.items = [...state.items]
|
|
526
|
+
await tick()
|
|
480
527
|
is(el.textContent, '123')
|
|
481
528
|
})
|
|
482
529
|
|
|
@@ -499,7 +546,7 @@ test('each: key-based caching is in-sync with direct elements', () => {
|
|
|
499
546
|
is(el.outerHTML, el2.outerHTML)
|
|
500
547
|
})
|
|
501
548
|
|
|
502
|
-
test('on: base', () => {
|
|
549
|
+
test('on: base', async () => {
|
|
503
550
|
let el = h`<div :on="{click(e){log.push('click')}, x}"></div>`
|
|
504
551
|
let log = []
|
|
505
552
|
let params = sprae(el, {x(){log.push('x')}, log})
|
|
@@ -511,11 +558,13 @@ test('on: base', () => {
|
|
|
511
558
|
is(log, ['click','x'])
|
|
512
559
|
|
|
513
560
|
params.x = function(){log.push('xx')}
|
|
561
|
+
await tick()
|
|
514
562
|
el.dispatchEvent(new window.Event('x'));
|
|
515
563
|
is(log, ['click','x','xx']);
|
|
516
564
|
|
|
517
565
|
console.log('make null')
|
|
518
566
|
params.x = null;
|
|
567
|
+
await tick()
|
|
519
568
|
el.dispatchEvent(new window.Event('x'));
|
|
520
569
|
is(log, ['click','x','xx']);
|
|
521
570
|
})
|
|
@@ -632,7 +681,7 @@ test('on: parallel chains', e => {
|
|
|
632
681
|
is(state.log, ['x','x','y','y','z','z','x']);
|
|
633
682
|
})
|
|
634
683
|
|
|
635
|
-
test('on: state changes between chain of events', e => {
|
|
684
|
+
test('on: state changes between chain of events', async e => {
|
|
636
685
|
let el = h`<x :on="{'x..y':fn}"></x>`
|
|
637
686
|
let log = []
|
|
638
687
|
let state = sprae(el, {log, fn: () => (log.push('x1'), ()=>log.push('y1'))})
|
|
@@ -641,6 +690,7 @@ test('on: state changes between chain of events', e => {
|
|
|
641
690
|
is(log, ['x1'])
|
|
642
691
|
console.log('update fn')
|
|
643
692
|
state.fn = () => (log.push('x2'), () => log.push('y2'))
|
|
693
|
+
await tick()
|
|
644
694
|
is(log, ['x1'])
|
|
645
695
|
// console.log('xx')
|
|
646
696
|
// NOTE: state update registers new chain listener before finishing prev chain
|
|
@@ -808,16 +858,17 @@ test('on: modifiers chain', async e => {
|
|
|
808
858
|
is(state.log,['x', 'x'])
|
|
809
859
|
})
|
|
810
860
|
|
|
811
|
-
test('with: inline', () => {
|
|
812
|
-
let el = h`<x :
|
|
861
|
+
test('with: inline', async () => {
|
|
862
|
+
let el = h`<x :scope="{foo:'bar'}"><y :text="foo + baz"></y></x>`
|
|
813
863
|
let state = sprae(el, {baz: 'qux'})
|
|
814
864
|
// FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
|
|
815
865
|
is(el.innerHTML, `<y>barqux</y>`)
|
|
816
866
|
state.baz = 'quux'
|
|
867
|
+
await tick()
|
|
817
868
|
is(el.innerHTML, `<y>barquux</y>`)
|
|
818
869
|
})
|
|
819
870
|
test.skip('with: inline reactive', () => {
|
|
820
|
-
let el = h`<x :
|
|
871
|
+
let el = h`<x :scope="{foo:'bar'}"><y :text="foo + baz"></y></x>`
|
|
821
872
|
let baz = signal('qux')
|
|
822
873
|
sprae(el, {baz})
|
|
823
874
|
// FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
|
|
@@ -825,48 +876,53 @@ test.skip('with: inline reactive', () => {
|
|
|
825
876
|
baz.value = 'quux'
|
|
826
877
|
is(el.innerHTML, `<y>barquux</y>`)
|
|
827
878
|
})
|
|
828
|
-
test('with: data', () => {
|
|
829
|
-
let el = h`<x :
|
|
879
|
+
test('with: data', async () => {
|
|
880
|
+
let el = h`<x :scope="x"><y :text="foo"></y></x>`
|
|
830
881
|
let state = sprae(el, {x: {foo:'bar'}})
|
|
831
882
|
is(el.innerHTML, `<y>bar</y>`)
|
|
832
883
|
console.log('update', state.x)
|
|
833
884
|
state.x.foo = 'baz'
|
|
885
|
+
await tick()
|
|
834
886
|
// Object.assign(state, {x:{foo:'baz'}})
|
|
835
887
|
is(el.innerHTML, `<y>baz</y>`)
|
|
836
888
|
})
|
|
837
|
-
test('with: transparency', () => {
|
|
889
|
+
test('with: transparency', async () => {
|
|
838
890
|
// NOTE: y:text initializes through directive, not through parent
|
|
839
891
|
// therefore by default :text uses parent's state, not defined by element itself
|
|
840
|
-
let el = h`<x :
|
|
892
|
+
let el = h`<x :scope="{foo:'foo'}"><y :scope="b" :text="foo+bar"></y></x>`
|
|
841
893
|
let params = sprae(el, {b:{bar:'bar'}})
|
|
842
894
|
is(el.innerHTML, `<y>foobar</y>`)
|
|
843
895
|
params.b.bar = 'baz'
|
|
896
|
+
await tick()
|
|
844
897
|
is(el.innerHTML, `<y>foobaz</y>`)
|
|
845
898
|
})
|
|
846
899
|
test.skip('with: reactive transparency', () => {
|
|
847
|
-
let el = h`<x :
|
|
900
|
+
let el = h`<x :scope="{foo:1}"><y :scope="b.c" :text="foo+bar"></y></x>`
|
|
848
901
|
const bar = signal('2')
|
|
849
902
|
sprae(el, {b:{c:{bar}}})
|
|
850
903
|
is(el.innerHTML, `<y>12</y>`)
|
|
851
904
|
bar.value = '3'
|
|
852
905
|
is(el.innerHTML, `<y>13</y>`)
|
|
853
906
|
})
|
|
854
|
-
test('with: writes to state', () => {
|
|
855
|
-
let a = h`<x :
|
|
907
|
+
test('with: writes to state', async () => {
|
|
908
|
+
let a = h`<x :scope="{a:1}"><y :on="{x(){a++}}" :text="a"></y></x>`
|
|
856
909
|
sprae(a)
|
|
857
910
|
is(a.innerHTML, `<y>1</y>`)
|
|
858
911
|
a.firstChild.dispatchEvent(new window.Event('x'))
|
|
912
|
+
await tick()
|
|
859
913
|
is(a.innerHTML, `<y>2</y>`)
|
|
860
914
|
a.firstChild.dispatchEvent(new window.Event('x'))
|
|
915
|
+
await tick()
|
|
861
916
|
is(a.innerHTML, `<y>3</y>`)
|
|
862
917
|
})
|
|
863
918
|
|
|
864
|
-
test('ref: base', () => {
|
|
919
|
+
test('ref: base', async () => {
|
|
865
920
|
let a = h`<a :ref="a" :init="log.push(a), null" :text="b"></a>`
|
|
866
921
|
let state = sprae(a, {log:[], b:1})
|
|
867
922
|
is(state.log[0], a)
|
|
868
923
|
is(a.outerHTML, `<a>1</a>`)
|
|
869
924
|
state.b = 2
|
|
925
|
+
await tick()
|
|
870
926
|
is(a.outerHTML, `<a>2</a>`)
|
|
871
927
|
is(state.a, a, 'Exposes to the state');
|
|
872
928
|
})
|
|
@@ -903,7 +959,7 @@ test(':: scope refers to current element', async () => {
|
|
|
903
959
|
|
|
904
960
|
test(':: scope directives must come first', async () => {
|
|
905
961
|
// NOTE: we init attributes in order of definition
|
|
906
|
-
let a = h`<x :text="y" :
|
|
962
|
+
let a = h`<x :text="y" :scope="{y:1}" :ref="x"></x>`
|
|
907
963
|
sprae(a, {})
|
|
908
964
|
is(a.outerHTML, `<x>1</x>`)
|
|
909
965
|
})
|
package/test/index.html
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
<meta charset=utf-8>
|
|
3
3
|
<title>Test</title>
|
|
4
4
|
|
|
5
|
+
<script defer src="../sprae.auto.js"></script>
|
|
6
|
+
<div :scope="{data:1}">
|
|
7
|
+
<span :text="data"></span>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
5
10
|
<script async src="../node_modules/es-module-shims/dist/es-module-shims.js"></script>
|
|
6
11
|
<script type="importmap">
|
|
7
12
|
{
|
|
@@ -27,4 +32,4 @@
|
|
|
27
32
|
}
|
|
28
33
|
</script>
|
|
29
34
|
|
|
30
|
-
<script src="./test.js" type="module"></script>
|
|
35
|
+
<script src="./test.js" type="module"></script>
|
package/test/state.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {state, fx
|
|
1
|
+
import {state, fx} from '../src/state.js'
|
|
2
2
|
import t, {is, ok} from 'tst'
|
|
3
3
|
import signalStruct from 'signal-struct'
|
|
4
4
|
import { effect } from '@preact/signals-core'
|
|
5
|
+
import {tick} from 'wait-please'
|
|
5
6
|
|
|
6
|
-
t('state: basic', t => {
|
|
7
|
+
t('state: basic', async t => {
|
|
7
8
|
let s = state({x:0, y:1})
|
|
8
9
|
|
|
9
10
|
let xy; fx(() => xy = s.x + s.y)
|
|
@@ -12,12 +13,14 @@ t('state: basic', t => {
|
|
|
12
13
|
s.x = 2
|
|
13
14
|
console.log('set 3')
|
|
14
15
|
s.y = 3
|
|
16
|
+
await tick()
|
|
15
17
|
is(xy, 5, 'Eq')
|
|
16
18
|
s.y = 4
|
|
19
|
+
await tick()
|
|
17
20
|
is(xy, 6, 'Eq')
|
|
18
21
|
})
|
|
19
22
|
|
|
20
|
-
t('state: signal-struct basics', t => {
|
|
23
|
+
t('state: signal-struct basics', async t => {
|
|
21
24
|
let s = state({
|
|
22
25
|
x: 0,
|
|
23
26
|
y: 1,
|
|
@@ -39,8 +42,10 @@ t('state: signal-struct basics', t => {
|
|
|
39
42
|
is(xy, 1)
|
|
40
43
|
s.x = 2
|
|
41
44
|
s.y = 3
|
|
45
|
+
await tick()
|
|
42
46
|
is(xy, 5)
|
|
43
47
|
s.y = 4
|
|
48
|
+
await tick()
|
|
44
49
|
is(xy, 6)
|
|
45
50
|
|
|
46
51
|
// getters are computed
|
|
@@ -51,7 +56,7 @@ t('state: signal-struct basics', t => {
|
|
|
51
56
|
is(s.xy, 6)
|
|
52
57
|
})
|
|
53
58
|
|
|
54
|
-
t('state: deep props', () => {
|
|
59
|
+
t('state: deep props', async () => {
|
|
55
60
|
let s = state({
|
|
56
61
|
z: { r: 2, i: 3 }
|
|
57
62
|
})
|
|
@@ -60,17 +65,20 @@ t('state: deep props', () => {
|
|
|
60
65
|
let len; fx(() => (len = (s.z.r**2 + s.z.i**2)**0.5))
|
|
61
66
|
s.z.r = 3
|
|
62
67
|
s.z.i = 4
|
|
68
|
+
await tick()
|
|
63
69
|
is(len, 5)
|
|
64
70
|
s.z.r = 4
|
|
65
71
|
s.z.i = 3
|
|
72
|
+
await tick()
|
|
66
73
|
is(len, 5)
|
|
67
74
|
|
|
68
75
|
// updating internal objects/arrays turns them into signals too
|
|
69
76
|
s.z = { r: 5, i: 12}
|
|
77
|
+
await tick()
|
|
70
78
|
is(len, 13)
|
|
71
79
|
})
|
|
72
80
|
|
|
73
|
-
t('state: array', () => {
|
|
81
|
+
t('state: array', async () => {
|
|
74
82
|
let s = state({
|
|
75
83
|
w: [1,2]
|
|
76
84
|
})
|
|
@@ -79,23 +87,27 @@ t('state: array', () => {
|
|
|
79
87
|
let mult; fx(() => mult = s.w?.[0] * s.w?.[1] || 0)
|
|
80
88
|
is(mult, 2)
|
|
81
89
|
s.w = [3,4]
|
|
90
|
+
await tick()
|
|
82
91
|
is(mult,12)
|
|
83
92
|
|
|
84
93
|
// nullifying is fine
|
|
85
94
|
s.w = null
|
|
95
|
+
await tick()
|
|
86
96
|
is(mult, 0)
|
|
87
97
|
|
|
88
98
|
// delete is fine
|
|
89
99
|
delete s.w
|
|
90
100
|
|
|
101
|
+
await tick()
|
|
91
102
|
is(mult, 0)
|
|
92
103
|
|
|
93
104
|
console.log('set w')
|
|
94
105
|
s.w = [1,2]
|
|
106
|
+
await tick()
|
|
95
107
|
is(mult, 2)
|
|
96
108
|
})
|
|
97
109
|
|
|
98
|
-
t('state: bulk-update', () => {
|
|
110
|
+
t('state: bulk-update', async () => {
|
|
99
111
|
let s = state({
|
|
100
112
|
x: 0,
|
|
101
113
|
y: 1,
|
|
@@ -114,6 +126,7 @@ t('state: bulk-update', () => {
|
|
|
114
126
|
// let [signals, update] = s
|
|
115
127
|
// update({ x: 1, y: 1, z: { r: 3, i: 4 } })
|
|
116
128
|
Object.assign(s, { x: 1, y: 1, z: { r: 3, i: 4 } })
|
|
129
|
+
await tick()
|
|
117
130
|
is(xy, 2)
|
|
118
131
|
is(len, 5, 'len after update')
|
|
119
132
|
})
|
|
@@ -178,31 +191,37 @@ t('state: inheritance', () => {
|
|
|
178
191
|
// })
|
|
179
192
|
})
|
|
180
193
|
|
|
181
|
-
t('state: inheritance: updating values in chain', () => {
|
|
194
|
+
t('state: inheritance: updating values in chain', async () => {
|
|
182
195
|
let s1 = {x:1}
|
|
183
196
|
let s = state(s1, {y:2})
|
|
184
|
-
console.group('fx')
|
|
197
|
+
// console.group('fx')
|
|
185
198
|
let xy = 0; fx(() => xy = s.x + s.y);
|
|
186
|
-
console.groupEnd('fx')
|
|
199
|
+
// console.groupEnd('fx')
|
|
200
|
+
await tick()
|
|
187
201
|
is(xy, 3)
|
|
188
202
|
s.x++
|
|
203
|
+
await tick()
|
|
189
204
|
is(xy, 4)
|
|
190
205
|
console.log('y++')
|
|
191
206
|
s.y++
|
|
207
|
+
await tick()
|
|
192
208
|
is(xy, 5)
|
|
193
209
|
})
|
|
194
210
|
|
|
195
|
-
t('state: array items', () => {
|
|
211
|
+
t('state: array items', async () => {
|
|
196
212
|
// arrays get each item converted to signal struct
|
|
197
213
|
let s5 = state({list: [{x:1}, {x:2}]})
|
|
198
214
|
let sum; fx(()=> sum = s5.list.reduce((sum, item)=>item.x + sum, 0))
|
|
199
215
|
is(sum, 3)
|
|
200
216
|
s5.list[0].x = 2
|
|
217
|
+
await tick()
|
|
201
218
|
is(sum, 4)
|
|
202
219
|
console.log('set array value')
|
|
203
220
|
s5.list = [{x:3}, {x:3}]
|
|
221
|
+
await tick()
|
|
204
222
|
is(sum, 6)
|
|
205
223
|
s5.list = [{x:3}, {x:3}, {x:4}]
|
|
224
|
+
await tick()
|
|
206
225
|
is(sum, 10)
|
|
207
226
|
})
|
|
208
227
|
|
|
@@ -214,14 +233,17 @@ t('state: arrays retain reference', () => {
|
|
|
214
233
|
is(list,[1,4,3])
|
|
215
234
|
})
|
|
216
235
|
|
|
217
|
-
t('state: direct list', () => {
|
|
236
|
+
t('state: direct list', async () => {
|
|
218
237
|
// works with arrays as well
|
|
219
238
|
let list = state([{x:1}, {x:2}])
|
|
220
239
|
let sum; fx(()=> sum = list.reduce((sum, item)=>item.x + sum, 0))
|
|
221
240
|
is(sum, 3)
|
|
222
241
|
list[0].x = 2
|
|
242
|
+
is(list[0].x, 2)
|
|
243
|
+
await tick()
|
|
223
244
|
is(sum, 4)
|
|
224
245
|
list.splice(0, 2, {x:3}, {x:3})
|
|
246
|
+
await tick()
|
|
225
247
|
console.log(list)
|
|
226
248
|
is(sum, 6)
|
|
227
249
|
})
|
|
@@ -240,7 +262,7 @@ t('state: circular?', () => {
|
|
|
240
262
|
is (a, [1, 2])
|
|
241
263
|
})
|
|
242
264
|
|
|
243
|
-
t('state: batch', () => {
|
|
265
|
+
t.skip('state: batch', () => {
|
|
244
266
|
let s = state({x:1, y:2})
|
|
245
267
|
let log = []; fx(() => log.push(s.x + s.y))
|
|
246
268
|
is(log, [3])
|
package/todo.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* [x] combinations: :else :if
|
|
9
9
|
* [x] :each :if, :if :each
|
|
10
10
|
* [x] :each :each
|
|
11
|
-
* [x] :
|
|
11
|
+
* [x] :scope must be able to write state value as well
|
|
12
12
|
* [x] docs: give example to each directive
|
|
13
13
|
* [x] initialize per-element: <x :each><y :if></y><x> - tree-dependent (:each comes first).
|
|
14
14
|
* [x] generalize common attributes :prop="xyz"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* [x] bulk events :ona:onb
|
|
23
23
|
* [x] multiprop setter :a:b="c"
|
|
24
24
|
* [x] make `this` in expression an element
|
|
25
|
-
* ~~[x] replace :ref with :
|
|
25
|
+
* ~~[x] replace :ref with :scope="this as x"~~
|
|
26
26
|
* [x] :ref creates instance in current state, not creates a new state
|
|
27
27
|
* [x] to avoid extending signal-struct, we must collect state data before, and call updates after for extended state
|
|
28
28
|
* [x] optimization: replace element-props with direct (better) setters
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
* ~~[x] report usignal problem~~ author is not really interested
|
|
32
32
|
* [x] `this` doesn't refer to element/scope in event handlers
|
|
33
33
|
* [x] :text="" empty values shouldn't throw
|
|
34
|
-
* [x] implement :
|
|
34
|
+
* [x] implement :scope
|
|
35
35
|
* [x] update :value without losing focus / position
|
|
36
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
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 :
|
|
38
|
+
* [x] :if :ref, :if :scope -> context setters must come first always
|
|
39
39
|
* [x] :style="{'--x':value}"
|
|
40
40
|
* [x] :onkeydown.ctrl-alt-D
|
|
41
41
|
* [ ] frameworks benchmark
|
|
@@ -46,4 +46,6 @@
|
|
|
46
46
|
* [x] once, capture, passive
|
|
47
47
|
* [x] ...rest
|
|
48
48
|
* [x] parallel chains
|
|
49
|
-
* [x] Sandbox
|
|
49
|
+
* [x] Sandbox
|
|
50
|
+
* [x] Autorun
|
|
51
|
+
* [ ] There's some weird bug with attaching event listeners to document, hmmm. Need test case.
|