sprae 2.14.1 → 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/test.js CHANGED
@@ -1,915 +1,2 @@
1
- // import { signal } from 'usignal/sync'
2
- import { signal } from '@preact/signals-core'
3
- import test, {is, any, throws} from 'tst'
4
- import {tick, time} from 'wait-please'
5
- import sprae from '../src/index.js'
6
- import h from 'hyperf'
7
-
8
- Object.defineProperty(DocumentFragment.prototype, 'outerHTML', {
9
- get() {
10
- let s = ''
11
- this.childNodes.forEach(n => {
12
- s += n.nodeType === 3 ? n.textContent : n.outerHTML != null ? n.outerHTML : ''
13
- })
14
- return s
15
- }
16
- })
17
-
18
- test.skip('autoinit', async () => {
19
- is(window.x.innerHTML, '1')
20
- })
21
-
22
- test('hidden: core', async () => {
23
- let el = h`<div :hidden="hidden"></div>`
24
- let params = sprae(el, {hidden:true})
25
- is(el.outerHTML, `<div hidden=""></div>`)
26
- params.hidden = false
27
- is(el.outerHTML, `<div></div>`)
28
- })
29
-
30
- test('hidden: reactive', async () => {
31
- const hidden = signal(true)
32
- let el = h`<div :hidden="hidden"></div>`
33
- sprae(el, {hidden})
34
- is(el.outerHTML, `<div hidden=""></div>`)
35
- hidden.value = false
36
- is(el.outerHTML, `<div></div>`)
37
- })
38
-
39
- test('common: reactive', async () => {
40
- let el = h`<label :for="name" :text="name" ></label><input :id="name" :name="name" :type="name" :disabled="!name"/><a :href="url"></a><img :src="url"/>`
41
- let params = sprae(el, {name:'text', url:'//google.com'})
42
- is(el.outerHTML, `<label for="text">text</label><input id="text" name="text" type="text"><a href="//google.com"></a><img src="//google.com">`)
43
- params.name = 'email'
44
- is(el.outerHTML, `<label for="email">email</label><input id="email" name="email" type="email"><a href="//google.com"></a><img src="//google.com">`)
45
- })
46
-
47
- test('common: empty strings', async () => {
48
- let el = h`<x :="" :x=""></x>`
49
- sprae(el)
50
- is(el.outerHTML, `<x></x>`)
51
- })
52
-
53
- test('common: comments', async () => {
54
- let el = h`<x :="/* */" :x="/* */"></x>`
55
- sprae(el)
56
- is(el.outerHTML, `<x></x>`)
57
- })
58
-
59
- test('common: newlines', async () => {
60
- let el = h`<x :text="
61
- x
62
- "></x>`
63
- sprae(el, {x:1})
64
- is(el.outerHTML, `<x>1</x>`)
65
- })
66
-
67
- test('style', async () => {
68
- let el = h`<x style="left: 1px" :style="style"></x>`
69
- let params = sprae(el, {style: "top: 1px"})
70
- is(el.outerHTML, `<x style="left: 1px; top: 1px"></x>`)
71
- params.style = {top: '2px'}
72
- is(el.outerHTML, `<x style="left: 1px; top: 2px;"></x>`)
73
-
74
- params.style = {'--x': 123}
75
- is(el.style.getPropertyValue('--x'), '123')
76
-
77
- params.style = {top:'1px', bottom:'2px'}
78
- is(el.outerHTML, `<x style="left: 1px; top: 1px; bottom: 2px;"></x>`)
79
-
80
- params.style = {top:'2px', bottom: null}
81
- // FIXME
82
- is(el.outerHTML, `<x style="left: 1px; top: 2px;"></x>`)
83
- })
84
-
85
- test('class', async () => {
86
- let el = h`<x class="base" :class="a"></x><y :class="[b, c]"></y><z :class="{b:true, c:d}"></z>`
87
- const c = signal('z')
88
- let params = sprae(el, {a:'x', b:'y', c, d:false});
89
- is(el.outerHTML, `<x class="base x"></x><y class="y z"></y><z class="b"></z>`);
90
- params.d = true;
91
- is(el.outerHTML, `<x class="base x"></x><y class="y z"></y><z class="b c"></z>`);
92
- c.value = 'w'
93
- is(el.outerHTML, `<x class="base x"></x><y class="y w"></y><z class="b c"></z>`);
94
- })
95
-
96
- test('props: base', async () => {
97
- let el = h`<input :id="0" :="{for:1, title:2, help:3, type:4, placeholder: 5, value: 6, aB: 8}" :value="7"/>`
98
- let params = sprae(el)
99
- is(el.outerHTML, `<input id="0" for="1" title="2" help="3" type="4" placeholder="5" value="7" a-b="8">`)
100
- })
101
-
102
- test('props: sets prop', async () => {
103
- let el = h`<x :x="this.x=1" :y="this.y='abc'"></x>`
104
- sprae(el)
105
- is(el.x, 1)
106
- is(el.y, 'abc')
107
- })
108
-
109
- test('props: multiprop', async () => {
110
- let el = h`<input :id:name:for="0" />`
111
- let params = sprae(el)
112
- is(el.outerHTML, `<input id="0" name="0" for="0">`)
113
- })
114
-
115
- // FIXME: this must work without return
116
- test.todo('props: calculation', async () => {
117
- let el = h`<x :x="let a = 5; return Array.from({length: x}, (_,i)=>i).join('')"></x>`
118
- let state = sprae(el, {x:3});
119
- is(el.outerHTML, `<x x="012"></x>`)
120
- state.x = 4
121
- is(el.outerHTML, `<x x="0123"></x>`)
122
- })
123
-
124
- test.todo('props: semicols in expression', async () => {
125
- let el = h`<x :x="0; return Array.from({length: x}, (_,i)=>i).join('')"></x>`
126
- let state = sprae(el, {x:3});
127
- is(el.outerHTML, `<x x="012"></x>`)
128
- state.x = 4
129
- is(el.outerHTML, `<x x="0123"></x>`)
130
- })
131
-
132
-
133
- test('data: base', async () => {
134
- let el = h`<input :data="{a:1, fooBar:2}"/>`
135
- let params = sprae(el)
136
- is(el.outerHTML, `<input data-a="1" data-foo-bar="2">`)
137
- })
138
-
139
- test('aria: base', async () => {
140
- let el = h`<input type="text" id="jokes" role="combobox" :aria="{controls:'joketypes', autocomplete:'list', expanded:false, activeOption:'item1', activedescendant:'', xxx:null}"/>`
141
- sprae(el)
142
- 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="">`)
143
- })
144
-
145
- test('value: direct', async () => {
146
- let el = h`<input :value="a" />`
147
- let state = sprae(el, {a:1})
148
- is(el.value, '1')
149
- is(el.outerHTML, `<input value="1">`)
150
- state.a = 2
151
- is(el.value, '2')
152
- is(el.outerHTML, `<input value="2">`)
153
-
154
- el.value = 3
155
- // el.dispatchEvent(new window.Event('change'))
156
- // is(state.a, '3')
157
- })
158
-
159
- test('value: textarea', async () => {
160
- let el = h`<textarea :value="a"></textarea>`
161
- let state = sprae(el, {a: 'abcdefgh'})
162
- is(el.selectionStart, 8)
163
- is(el.selectionEnd, 8)
164
- el.setSelectionRange(1, 4)
165
- is(el.selectionStart, 1)
166
- is(el.selectionEnd, 4)
167
- state.a = 'xyzyvw'
168
- is(el.selectionStart, 1)
169
- is(el.selectionEnd, 4)
170
- })
171
-
172
- test('text: core', async () => {
173
- let el = h`<div :text="text"></div>`
174
- let params = sprae(el, {text:'abc'})
175
- is(el.outerHTML, `<div>abc</div>`)
176
- params.text = null
177
- is(el.outerHTML, `<div></div>`)
178
- })
179
-
180
- test('if: base', async () => {
181
- let el = h`<p>
182
- <span :if="a==1">a</span>
183
- <span :else :if="a==2">b</span>
184
- <span :else >c</span>
185
- </p>`
186
-
187
- const params = sprae(el, { a: 1 })
188
-
189
- is(el.innerHTML, '<span>a</span>')
190
- params.a = 2
191
- is(el.innerHTML, '<span>b</span>')
192
- params.a = 3
193
- is(el.innerHTML, '<span>c</span>')
194
- params.a = null
195
- is(el.innerHTML, '<span>c</span>')
196
- })
197
-
198
- test('if: short with insertions', async () => {
199
- let el = h`<p>
200
- <span :if="a==1" :text="'1:'+a"></span>
201
- <span :else :if="a==2" :text="'2:'+a"></span>
202
- <span :else :text="a"></span>
203
- </p>`
204
-
205
- const params = sprae(el, { a: 1 })
206
-
207
- is(el.innerHTML, '<span>1:1</span>')
208
- params.a = 2
209
- is(el.innerHTML, '<span>2:2</span>')
210
- params.a = 3
211
- is(el.innerHTML, '<span>3</span>')
212
- params.a = 4
213
- is(el.innerHTML, '<span>4</span>')
214
-
215
- params.a = 1
216
- is(el.innerHTML, '<span>1:1</span>')
217
- params.a = 4
218
- is(el.innerHTML, '<span>4</span>')
219
-
220
- params.a = null
221
- })
222
-
223
- test('if: reactive values', async () => {
224
- let el = h`<p>
225
- <span :if="a==1" :text="'1:'+a"></span>
226
- <span :else :if="a==2" :text="'2:'+a"></span>
227
- <span :else :text="a"></span>
228
- </p>`
229
-
230
- const a = signal(1)
231
- sprae(el, { a })
232
-
233
- is(el.innerHTML, '<span>1:1</span>')
234
- a.value = 2
235
- is(el.innerHTML, '<span>2:2</span>')
236
- a.value = 3
237
- is(el.innerHTML, '<span>3</span>')
238
- a.value = 4
239
- is(el.innerHTML, '<span>4</span>')
240
-
241
- a.value = 1
242
- is(el.innerHTML, '<span>1:1</span>')
243
- a.value = 4
244
- is(el.innerHTML, '<span>4</span>')
245
- })
246
-
247
- test('if: (#3) subsequent content is not abandoned', async () => {
248
- let x = h`<x><y :if="!!y"></y><z :text="123"></z></x>`
249
- sprae(x, {y: false})
250
- is(x.outerHTML, `<x><z>123</z></x>`)
251
- })
252
-
253
- test('if: + :with doesnt prevent secondary effects from happening', () => {
254
- let el = h`<div><x :if="x" :with="{}" :text="x"></x></div>`
255
- let state = sprae(el, {x:''})
256
- is(el.innerHTML, ``)
257
- state.x = '123'
258
- is(el.innerHTML, `<x>123</x>`)
259
-
260
- // NOTE: we ignore this case
261
- // let el2 = h`<div><x :if="x" :with="{x:cond}" :text="x"></x></div>`
262
- // let state2 = sprae(el, {cond:''})
263
- // is(el2.innerHTML, ``)
264
- // state2.cond = '123'
265
- // is(el2.innerHTML, `<x>123</x>`)
266
- })
267
-
268
- test('each: array', async () => {
269
- // FIXME: in some conspicuous reason jsdom fails to update text nodes somehow
270
- let el = h`<p>
271
- <span :each="a in b" :text="a"></span>
272
- </p>`
273
-
274
- const params = sprae(el, { b: [] })
275
-
276
- is(el.innerHTML, '')
277
- console.log('set 1,2')
278
- params.b = [1,2]
279
- is(el.innerHTML, '<span>1</span><span>2</span>')
280
- params.b = []
281
- is(el.innerHTML, '')
282
- params.b = null
283
- is(el.innerHTML, '')
284
- })
285
-
286
- test('each: object', async () => {
287
- // FIXME: in some conspicuous reason jsdom fails to update text nodes somehow
288
- let el = h`<p>
289
- <span :each="x,key in b" :text="[key,x]"></span>
290
- </p>`
291
-
292
- const params = sprae(el, { b: null })
293
-
294
- is(el.innerHTML, '')
295
- console.log('set 1,2')
296
- params.b = { x:1, y:2 }
297
- is(el.innerHTML, '<span>x,1</span><span>y,2</span>')
298
- params.b = []
299
- is(el.innerHTML, '')
300
- params.b = null
301
- is(el.innerHTML, '')
302
- })
303
-
304
- test('each: loop within loop', async () => {
305
- let el = h`<p>
306
- <x :each="b in c"><y :each="a in b" :text="a"></y></x>
307
- </p>`
308
-
309
- const params = sprae(el, { c: [[1,2], [3,4]] })
310
-
311
- is(el.innerHTML, '<x><y>1</y><y>2</y></x><x><y>3</y><y>4</y></x>')
312
- params.c = [[5,6], [3,4]]
313
- is(el.innerHTML, '<x><y>5</y><y>6</y></x><x><y>3</y><y>4</y></x>')
314
- // params.c[1] = [7,8]
315
- params.c = [params.c[0], [7,8]]
316
- is(el.innerHTML, '<x><y>5</y><y>6</y></x><x><y>7</y><y>8</y></x>')
317
- // is(el.innerHTML, '<span>1</span><span>2</span>')
318
- params.c = []
319
- is(el.innerHTML, '')
320
- // params.b = null
321
- // is(el.innerHTML, '')
322
- })
323
-
324
- test('each: reactive values', async () => {
325
- let el = h`<p>
326
- <span :each="a in b" :text="a"></span>
327
- </p>`
328
-
329
- const b = signal([])
330
- const params = sprae(el, { b })
331
-
332
- is(el.innerHTML, '')
333
- b.value = [1,2]
334
- is(el.innerHTML, '<span>1</span><span>2</span>')
335
- b.value = []
336
- is(el.innerHTML, '')
337
- params.b = null
338
- is(el.innerHTML, '')
339
- })
340
-
341
- test('each: loop with condition', async () => {
342
- // NOTE: there doesn't seem to be much value in exactly that
343
- // also it creates confusion with :else directive
344
- // prohibitin that allows in-order directives init
345
- let el = h`<p>
346
- <span :each="a in b" :text="a" :if="c"></span>
347
- </p>`
348
-
349
- const params = sprae(el, { b: [1,2], c: false })
350
-
351
- is(el.innerHTML, '')
352
- params.c = true
353
- is(el.innerHTML, '<span>1</span><span>2</span>')
354
- params.b = [1]
355
- is(el.innerHTML, '<span>1</span>')
356
- params.b = null
357
- is(el.innerHTML, '')
358
- })
359
-
360
- test('each: condition with loop', async () => {
361
- let el = h`<p>
362
- <span :if="c" :each="a in b" :text="a"></span>
363
- <span :else :text="c"></span>
364
- </p>`
365
-
366
- const params = sprae(el, { b: [1,2], c: false })
367
-
368
- is(el.innerHTML, '<span>false</span>')
369
- params.c = true
370
- is(el.innerHTML, '<span>1</span><span>2</span>')
371
- params.b = [1]
372
- is(el.innerHTML, '<span>1</span>')
373
- params.b = null
374
- is(el.innerHTML, '')
375
- console.log('c=false')
376
- params.c = false
377
- is(el.innerHTML, '<span>false</span>')
378
- })
379
-
380
- test('each: loop within condition', async () => {
381
- let el = h`<p>
382
- <x :if="a==1"><y :each="i in a" :text="i"></y></x>
383
- <x :else :if="a==2"><y :each="i in a" :text="-i"></y></x>
384
- </p>`
385
-
386
- const params = sprae(el, { a: 1 })
387
-
388
- is(el.innerHTML, '<x><y>1</y></x>')
389
- params.a = 2
390
- is(el.innerHTML, '<x><y>-1</y><y>-2</y></x>')
391
- params.a = 0
392
- is(el.innerHTML, '')
393
- })
394
-
395
- test('each: condition within loop', async () => {
396
- let el = h`<p>
397
- <x :each="a in b">
398
- <y :if="a==1" :text="'1:'+a"></y>
399
- <y :else :if="a==2" :text="'2:'+a"></y>
400
- <y :else :text="a"></y>
401
- </x>
402
- </p>`
403
-
404
- const params = sprae(el, { b: [1,2,3] })
405
-
406
- is(el.innerHTML, '<x><y>1:1</y></x><x><y>2:2</y></x><x><y>3</y></x>')
407
- params.b = [2]
408
- is(el.innerHTML, '<x><y>2:2</y></x>')
409
- params.b = null
410
- is(el.innerHTML, '')
411
- })
412
-
413
- test('each: next items have own "this", not single one', async () => {
414
- // FIXME: let el = h`<x :each="x in 3"></x>`
415
- let el = h`<div><x :each="x in 3" :data="{x}" :x="log.push(x, this.dataset.x)"></x></div>`
416
- let log = []
417
- let state = sprae(el, {log})
418
- is(state.log, [1,'1',2,'2',3,'3'])
419
- })
420
-
421
- test('each: unkeyed', async () => {
422
- let el = h`<div><x :each="x in xs" :text="x"></x></div>`
423
- let state = sprae(el, {xs:[1,2,3]})
424
- is(el.children.length, 3)
425
- is(el.textContent, '123')
426
- // let first = el.firstChild
427
- state.xs = [1,3,2]
428
- // is(el.firstChild, first)
429
- is(el.textContent, '132')
430
- state.xs = [3,3,3]
431
- is(el.textContent, '333')
432
- // is(el.firstChild, first)
433
- })
434
-
435
- test('each: keyed', async () => {
436
- // keyed
437
- let el = h`<div><x :each="x in xs" :text="x" :key="x"></x></div>`
438
- let state = sprae(el, {xs:[1,2,3]})
439
- is(el.children.length, 3)
440
- is(el.textContent, '123')
441
- let first = el.firstChild
442
- state.xs = [1,3,2]
443
- is(el.firstChild, first)
444
- is(el.textContent, '132')
445
- state.xs = [3,3,3]
446
- is(el.textContent, '3')
447
- // is(el.firstChild, first)
448
- })
449
-
450
- test('each: wrapped source', async () => {
451
- let el = h`<div><x :each="i in (x || 2)" :text="i"></x></div>`
452
- sprae(el, {x:0})
453
- is(el.innerHTML, `<x>1</x><x>2</x>`)
454
- })
455
-
456
- test.todo('each: unmounted elements remove listeners', async () => {
457
- // let's hope they get removed without memory leaks :')
458
- })
459
-
460
- test('each: internal children get updated by state update, also: update by running again', () => {
461
- let el = h`<><x :each="item, idx in items" :text="item" :key="idx"></x></>`
462
- let state = sprae(el, { items: [1,2,3] })
463
- is(el.textContent, '123')
464
- state.items = [2, 2, 3]
465
- is(el.textContent, '223')
466
- state = sprae(el, { items: [0,2,3] })
467
- is(el.textContent, '023')
468
- // NOTE: this doesn't update items, since they're new array
469
- console.log('set items')
470
- state.items[0] = 1
471
- state.items = [...state.items]
472
- is(el.textContent, '123')
473
- })
474
-
475
- test('each: :id and others must receive value from context', () => {
476
- let el = h`<div><x :id="idx" :each="item, idx in items"></x></div>`
477
- sprae(el, {items:[1,2,3]})
478
- is(el.innerHTML,`<x id="1"></x><x id="2"></x><x id="3"></x>`)
479
- })
480
-
481
- test('each: key-based caching is in-sync with direct elements', () => {
482
- let el = h`<ul><li :each="i in x" :key="i" :id="i"></li></ul>`
483
- let el2 = h`<ul><li :each="i in x" :id="i"></li></ul>`
484
- let state = sprae(el, {x:2})
485
- let state2 = sprae(el2, {x:2})
486
- is(el.outerHTML, el2.outerHTML)
487
- el.firstChild.after(el.firstChild.cloneNode(true))
488
- el2.firstChild.after(el2.firstChild.cloneNode(true))
489
- state.x = 3
490
- state2.x = 3
491
- is(el.outerHTML, el2.outerHTML)
492
- })
493
-
494
- test('on: base', () => {
495
- let el = h`<div :on="{click(e){log.push('click')}, x}"></div>`
496
- let log = signal([])
497
- let params = sprae(el, {x(){log.value.push('x')}, log})
498
-
499
- is(el.outerHTML, `<div></div>`);
500
- el.dispatchEvent(new window.Event('click'));
501
- is(log.value, ['click'])
502
- el.dispatchEvent(new window.Event('x'));
503
- is(log.value, ['click','x'])
504
-
505
- params.x = function(){log.value.push('xx')}
506
- el.dispatchEvent(new window.Event('x'));
507
- is(log.value, ['click','x','xx']);
508
-
509
- console.log('make null')
510
- params.x = null;
511
- el.dispatchEvent(new window.Event('x'));
512
- is(log.value, ['click','x','xx']);
513
- })
514
-
515
- test('onevt: this context', e => {
516
- let el = h`<div :onx="function(){log.push(this)}"></div>`
517
- let state = sprae(el, {log: []})
518
- el.dispatchEvent(new window.Event('x'));
519
- is(state.log, [el])
520
- })
521
-
522
- test('on: this in chains refers to el', () => {
523
- let el = h`<div :ona..onb="function(e){x=this; log.push(1); return () => log.push(2)}"></div>`
524
- let state = sprae(el, {log:[], x:null})
525
- el.dispatchEvent(new window.Event('a'));
526
- is(state.log, [1])
527
- })
528
-
529
- test('on: multiple events', e => {
530
- let el = h`<div :onscroll:onclick:onx="e=>log.push(e.type)"></div>`
531
- let state = sprae(el, {log:[]})
532
-
533
- el.dispatchEvent(new window.Event('click'));
534
- is(state.log, ['click'])
535
- el.dispatchEvent(new window.Event('scroll'));
536
- is(state.log, ['click','scroll'])
537
- el.dispatchEvent(new window.Event('x'));
538
- is(state.log, ['click','scroll','x'])
539
- })
540
-
541
- test('on: in-out events', e => {
542
- // let el = document.createElement('x');
543
- // el.setAttribute(':onmousedown..onmouseup', 'e=>(log.push(e.type),e=>log.push(e.type))')
544
- let el = h`<x :onmousedown..onmouseup="(e) => { x=this; log.push(e.type); return e=>log.push(e.type); }"></x>`
545
-
546
- let state = sprae(el, {log:[],x:null})
547
- el.dispatchEvent(new window.Event('mousedown'));
548
- is(state.x, el);
549
- is(state.log, ['mousedown'])
550
- el.dispatchEvent(new window.Event('mouseup'));
551
- is(state.log, ['mousedown','mouseup'])
552
- })
553
-
554
- test('on: in-out side-effects', e => {
555
- let log = []
556
-
557
- // 1. skip in event and do directly out
558
- let el = h`<x :onin..onout="io"></x>`
559
- sprae(el, { io(e) {
560
- log.push(e.type)
561
- return (e) => (log.push(e.type), [1,2,3])
562
- } })
563
-
564
- el.dispatchEvent(new window.Event('out'));
565
- is(log, [])
566
-
567
- // 2. Some nonsensical return is fine
568
- el.dispatchEvent(new window.Event('in'));
569
- is(log, ['in'])
570
- el.dispatchEvent(new window.Event('out'));
571
- is(log, ['in','out'], 'out triggers right')
572
- el.dispatchEvent(new window.Event('out'));
573
- is(log, ['in','out'])
574
- el.dispatchEvent(new window.Event('in'));
575
- is(log, ['in','out','in'])
576
- el.dispatchEvent(new window.Event('in'));
577
- is(log, ['in','out','in', 'in'])
578
- el.dispatchEvent(new window.Event('out'));
579
- is(log, ['in','out','in','in','out','out'])
580
- el.dispatchEvent(new window.Event('out'));
581
- is(log, ['in','out','in','in','out','out'])
582
- })
583
-
584
- test('on: chain of events', e => {
585
- let el = h`<div :onmousedown..onmousemove..onmouseup="e=>(log.push(e.type),e=>(log.push(e.type),e=>log.push(e.type)))"></div>`
586
- let state = sprae(el, {log:[]})
587
-
588
- el.dispatchEvent(new window.Event('mousedown'));
589
- is(state.log, ['mousedown'])
590
- el.dispatchEvent(new window.Event('mousemove'));
591
- is(state.log, ['mousedown','mousemove'])
592
- el.dispatchEvent(new window.Event('mouseup'));
593
- is(state.log, ['mousedown','mousemove','mouseup'])
594
- el.dispatchEvent(new window.Event('mouseup'));
595
- is(state.log, ['mousedown','mousemove','mouseup'])
596
- el.dispatchEvent(new window.Event('mousedown'));
597
- is(state.log, ['mousedown','mousemove','mouseup','mousedown'])
598
- })
599
-
600
- test('on: parallel chains', e => {
601
- let el = h`<div :onx..ony..onz="e=>('x',log.push(e.type),e=>('y',log.push(e.type),e=>('z',log.push(e.type))))"></div>`
602
- let state = sprae(el, {log:[]})
603
-
604
- console.log('emit x')
605
- el.dispatchEvent(new window.Event('x'));
606
- is(state.log, ['x'])
607
- console.log('emit x')
608
- el.dispatchEvent(new window.Event('x'));
609
- is(state.log, ['x','x'])
610
- console.log('emit y')
611
- el.dispatchEvent(new window.Event('y'));
612
- is(state.log, ['x','x','y','y'])
613
- console.log('emit y')
614
- el.dispatchEvent(new window.Event('y'));
615
- is(state.log, ['x','x','y','y'])
616
- console.log('emit z')
617
- el.dispatchEvent(new window.Event('z'));
618
- console.log('emit y')
619
- el.dispatchEvent(new window.Event('y'));
620
- is(state.log, ['x','x','y','y','z','z'])
621
- el.dispatchEvent(new window.Event('z'));
622
- is(state.log, ['x','x','y','y','z','z']);
623
- el.dispatchEvent(new window.Event('x'));
624
- is(state.log, ['x','x','y','y','z','z','x']);
625
- })
626
-
627
- test('on: state changes between chain of events', e => {
628
- let el = h`<x :on="{'x..y':fn}"></x>`
629
- let log = []
630
- let state = sprae(el, {log, fn: () => (log.push('x1'), ()=>log.push('y1'))})
631
- console.log('emit x')
632
- el.dispatchEvent(new window.Event('x'));
633
- is(log, ['x1'])
634
- console.log('update fn')
635
- state.fn = () => (log.push('x2'), () => log.push('y2'))
636
- is(log, ['x1'])
637
- // console.log('xx')
638
- // NOTE: state update registers new chain listener before finishing prev chain
639
- // el.dispatchEvent(new window.Event('x'));
640
- // el.dispatchEvent(new window.Event('x'));
641
- // is(log, [1])
642
- console.log('emit y, y')
643
- el.dispatchEvent(new window.Event('y'));
644
- el.dispatchEvent(new window.Event('y'));
645
- is(log, ['x1', 'y1'])
646
- console.log('emit x')
647
- el.dispatchEvent(new window.Event('x'));
648
- is(log, ['x1','y1','x2'])
649
- console.log('emit y, y')
650
- el.dispatchEvent(new window.Event('y'));
651
- el.dispatchEvent(new window.Event('y'));
652
- is(log, ['x1','y1','x2','y2'])
653
- })
654
-
655
- test('on: once', e => {
656
- // NOTE: if callback updates it's still rebound
657
- let el = h`<x :onx.once="(e=>x&&log.push(this))" ></x>`
658
- let log = []
659
- let state = sprae(el, {log, x:1})
660
- el.dispatchEvent(new window.Event('x'));
661
- is(log, [el])
662
- el.dispatchEvent(new window.Event('x'));
663
- is(log, [el])
664
- state.x = 2
665
- el.dispatchEvent(new window.Event('x'));
666
- el.dispatchEvent(new window.Event('x'));
667
- is(log, [el])
668
- })
669
-
670
- test('on: capture, stop, prevent', e => {
671
- let el = h`<x :onx.capture="e=>log.push(1)"><y :onx="e=>log.push(2)"></y></x>`
672
- let state = sprae(el, {log:[]})
673
- el.firstChild.dispatchEvent(new window.Event('x', {bubbles:true}));
674
- is(state.log, [1,2])
675
-
676
- let el2 = h`<x :onx="e=>log.push(1)"><y :onx.stop="e=>log.push(2)"></y></x>`
677
- let state2 = sprae(el2, {log:[]})
678
- el2.firstChild.dispatchEvent(new window.Event('x', {bubbles:true}));
679
- is(state2.log, [2])
680
- })
681
-
682
- test('on: window, self', e => {
683
- let el = h`<x :onx.self="e=>log.push(1)"><y :onx.window="e=>log.push(2)"></y></x>`
684
- let state = sprae(el, {log:[]})
685
- el.firstChild.dispatchEvent(new window.Event('x', {bubbles:true}));
686
- is(state.log, [])
687
- el.dispatchEvent(new window.Event('x', {bubbles:true}));
688
- is(state.log, [1])
689
- window.dispatchEvent(new window.Event('x', {bubbles:true}));
690
- is(state.log, [1,2])
691
- })
692
-
693
- test('on: keys', e => {
694
- let el = h`<x :onkeydown.enter="e=>log.push(1)"></x>`
695
- let state = {log:[]}
696
- sprae(el, state)
697
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: '' }));
698
- is(state.log,[])
699
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
700
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: '' }));
701
- is(state.log,[1])
702
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
703
- is(state.log,[1,1])
704
- })
705
-
706
- test('on: key combinations', e => {
707
- let el = h`<x :onkeydown.ctrl-enter="e=>log.push(1)"></x>`
708
- let state = {log:[]}
709
- sprae(el, state)
710
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: '' }));
711
- is(state.log,[])
712
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
713
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: '' }));
714
- is(state.log,[])
715
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true }));
716
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
717
- is(state.log,[1])
718
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter' }));
719
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true }));
720
- is(state.log,[1,1])
721
- let el2 = h`<x :onkeydown.ctrl-alt-enter="e=>log.push(1)"></x>`
722
- })
723
-
724
- test('on: keys with prevent', e => {
725
- let el = h`<y :onkeydown="e=>log.push(e.key)"><x :ref="x" :onkeydown.enter.stop></x></y>`
726
- let state = sprae(el, {log:[]})
727
- state.x.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
728
- console.log('enter')
729
- state.x.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
730
- is(state.log,['x'])
731
- })
732
-
733
- test('on: debounce', async e => {
734
- let el = h`<x :onkeydown.debounce-1="e=>log.push(e.key)"></x>`
735
- let state = sprae(el, {log:[]})
736
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
737
- is(state.log, [])
738
- await time(2)
739
- is(state.log, ['x'])
740
- })
741
-
742
- test('on: debounce 0', async e => {
743
- let el = h`<x :onkeydown.debounce-0="e=>log.push(e.key)"></x>`
744
- let state = sprae(el, {log:[]})
745
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
746
- is(state.log, [])
747
- await time(2)
748
- is(state.log, ['x'])
749
- })
750
-
751
- test('on: throttle', async e => {
752
- let el = h`<x :onkeydown.throttle-10="e=>log.push(e.key)"></x>`
753
- let state = sprae(el, {log:[]})
754
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
755
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
756
- is(state.log, ['x'])
757
- await time(5)
758
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
759
- is(state.log, ['x'])
760
- await time(10)
761
- is(state.log, ['x', 'x'])
762
- await time(10)
763
- is(state.log, ['x', 'x'])
764
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
765
- is(state.log, ['x', 'x', 'x'])
766
- })
767
-
768
- test('on: toggle', async e => {
769
- let el = h`<x :onx.toggle="e=>(log.push(1),e=>log.push(2))"></x>`
770
- let state = sprae(el, {log:[]})
771
- el.dispatchEvent(new window.KeyboardEvent('x'));
772
- is(state.log, [1])
773
- el.dispatchEvent(new window.KeyboardEvent('x'));
774
- is(state.log, [1,2])
775
- el.dispatchEvent(new window.KeyboardEvent('x'));
776
- is(state.log, [1,2,1])
777
- el.dispatchEvent(new window.KeyboardEvent('x'));
778
- is(state.log, [1,2,1,2])
779
- })
780
-
781
- test.skip('on: nexttick', async e => {
782
- let el = h`<x :onkeydown.nexttick="e=>log.push(e.key)"></x>`
783
- let state = sprae(el, {log:[]})
784
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
785
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
786
- is(state.log, [])
787
- await time()
788
- is(state.log, ['x', 'x'])
789
- })
790
-
791
- test('on: modifiers chain', async e => {
792
- let el = h`<x :onkeydown.letter..onkeyup.letter="e=>(log.push(e.key),(e)=>log.push(e.key))"></x>`
793
- let state = sprae(el, {log:[]})
794
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'x', bubbles: true }));
795
- el.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
796
- is(state.log,['x'])
797
- el.dispatchEvent(new window.KeyboardEvent('keyup', { key: 'Enter', bubbles: true }));
798
- is(state.log,['x'])
799
- el.dispatchEvent(new window.KeyboardEvent('keyup', { key: 'x', bubbles: true }));
800
- is(state.log,['x', 'x'])
801
- })
802
-
803
- test('with: inline', () => {
804
- let el = h`<x :with="{foo:'bar'}"><y :text="foo + baz"></y></x>`
805
- let state = sprae(el, {baz: 'qux'})
806
- // FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
807
- is(el.innerHTML, `<y>barqux</y>`)
808
- state.baz = 'quux'
809
- is(el.innerHTML, `<y>barquux</y>`)
810
- })
811
- test('with: inline reactive', () => {
812
- let el = h`<x :with="{foo:'bar'}"><y :text="foo + baz"></y></x>`
813
- let baz = signal('qux')
814
- sprae(el, {baz})
815
- // FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
816
- is(el.innerHTML, `<y>barqux</y>`)
817
- baz.value = 'quux'
818
- is(el.innerHTML, `<y>barquux</y>`)
819
- })
820
- test('with: data', () => {
821
- let el = h`<x :with="x"><y :text="foo"></y></x>`
822
- let state = sprae(el, {x:{foo:'bar'}})
823
- is(el.innerHTML, `<y>bar</y>`)
824
- console.log('update')
825
- state.x.foo = 'baz'
826
- // Object.assign(state, {x:{foo:'baz'}})
827
- is(el.innerHTML, `<y>baz</y>`)
828
- })
829
- test('with: transparency', () => {
830
- // NOTE: y:text initializes through directive, not through parent
831
- // therefore by default :text uses parent's state, not defined by element itself
832
- let el = h`<x :with="{foo:'foo'}"><y :with="b" :text="foo+bar"></y></x>`
833
- let params = sprae(el, {b:{bar:'bar'}})
834
- is(el.innerHTML, `<y>foobar</y>`)
835
- params.b.bar = 'baz'
836
- is(el.innerHTML, `<y>foobaz</y>`)
837
- })
838
- test('with: reactive transparency', () => {
839
- let el = h`<x :with="{foo:1}"><y :with="b.c" :text="foo+bar"></y></x>`
840
- const bar = signal('2')
841
- sprae(el, {b:{c:{bar}}})
842
- is(el.innerHTML, `<y>12</y>`)
843
- bar.value = '3'
844
- is(el.innerHTML, `<y>13</y>`)
845
- })
846
- test('with: writes to state', () => {
847
- let a = h`<x :with="{a:1}"><y :on="{x(){a++}}" :text="a"></y></x>`
848
- sprae(a)
849
- is(a.innerHTML, `<y>1</y>`)
850
- a.firstChild.dispatchEvent(new window.Event('x'))
851
- is(a.innerHTML, `<y>2</y>`)
852
- a.firstChild.dispatchEvent(new window.Event('x'))
853
- is(a.innerHTML, `<y>3</y>`)
854
- })
855
-
856
- test('ref: base', () => {
857
- let a = h`<a :ref="a" :init="log.push(a), null" :text="b"></a>`
858
- let state = sprae(a, {log:[], b:1})
859
- is(state.log[0], a)
860
- is(a.outerHTML, `<a>1</a>`)
861
- state.b = 2
862
- is(a.outerHTML, `<a>2</a>`)
863
- is(state.a, a, 'Exposes to the state');
864
- })
865
-
866
- test('ref: with :each', () => {
867
- let a = h`<y><x :ref="x" :each="item in items" :text="log.push(x), item"/></y>`
868
- let state = sprae(a, {log: [], items: [1,2]})
869
- is(a.innerHTML, `<x>1</x><x>2</x>`)
870
- is(state.log, [...a.children])
871
- })
872
-
873
- test(':: reactive values', async () => {
874
- let a = new Promise((ok) => setTimeout(() => ok(2), 10))
875
-
876
- let el = h`<x :text="a">1</x>`
877
- sprae(el, {a})
878
- is(el.outerHTML, `<x></x>`)
879
-
880
- await time(20)
881
- is(el.outerHTML, `<x>2</x>`)
882
- })
883
-
884
- test(':: null result does nothing', async () => {
885
- let a = h`<x :="undefined"></x>`
886
- sprae(a)
887
- is(a.outerHTML, `<x></x>`)
888
- })
889
-
890
- test(':: scope refers to current element', async () => {
891
- let el = h`<x :text="log.push(this)"></x>`
892
- let state = sprae(el, {log:[]})
893
- is(state.log, [el])
894
- })
895
-
896
- test.todo(':: scope directives must come first', async () => {
897
- // NOTE: we init attributes in order of definition
898
- let a = h`<x :text="y" :with="{y:1}" :ref="x"></x>`
899
- sprae(a, {})
900
- is(a.outerHTML, `<x>1</x>`)
901
- })
902
-
903
- test.todo('getters', async () => {
904
- let x = h`<x>
905
- <h2 :if="doubledCount > 10">YAY!</h2>
906
- <button :text="count" :on="{click:increment}"/>
907
- <button :text="doubledCount" :on="{click:increment}"/>
908
- </x>`
909
- document.body.appendChild(x)
910
- let state = sprae(x, {
911
- count:0,
912
- get doubledCount(){ console.log(this); return this.count * 2},
913
- increment(){ this.count++ }
914
- })
915
- })
1
+ import './state.js'
2
+ import './dom.js'