sprae 0.1.0 → 2.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,100 +1,173 @@
1
- import v from 'value-ref'
2
- import { signal } from '@preact/signals'
1
+ // import { signal } from 'usignal'
2
+ import { signal } from '@preact/signals-core'
3
3
  import test, {is, any, throws} from 'tst'
4
4
  import {tick, time} from 'wait-please'
5
- import sprae from 'sprae'
5
+ import sprae from '../src/index.js'
6
6
  import h from 'hyperf'
7
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
+
8
22
  test('hidden: core', async () => {
9
23
  let el = h`<div :hidden="hidden"></div>`
10
24
  let params = sprae(el, {hidden:true})
11
- is(el.outerHTML, `<div class="∴hidden" hidden=""></div>`)
25
+ is(el.outerHTML, `<div hidden=""></div>`)
12
26
  params.hidden = false
13
- is(el.outerHTML, `<div class="∴hidden"></div>`)
27
+ is(el.outerHTML, `<div></div>`)
14
28
  })
15
29
 
16
30
  test('hidden: reactive', async () => {
17
31
  const hidden = signal(true)
18
32
  let el = h`<div :hidden="hidden"></div>`
19
33
  sprae(el, {hidden})
20
- is(el.outerHTML, `<div class="∴hidden" hidden=""></div>`)
34
+ is(el.outerHTML, `<div hidden=""></div>`)
21
35
  hidden.value = false
22
- is(el.outerHTML, `<div class="∴hidden"></div>`)
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: style', async () => {
48
+ let el = h`<x :style="style"></x>`
49
+ let params = sprae(el, {style: "top: 1px"})
50
+ is(el.outerHTML, `<x style="top: 1px"></x>`)
51
+ params.style = {top: '2px'}
52
+ is(el.outerHTML, `<x style="top: 2px"></x>`)
53
+ })
54
+
55
+ test('common: class', async () => {
56
+ let el = h`<x :class="a"></x><y :class="[b, c]"></y><z :class="{b:true, c:d}"></z>`
57
+ const c = signal('z')
58
+ let params = sprae(el, {a:'x', b:'y', c, d:false});
59
+ is(el.outerHTML, `<x class="x"></x><y class="y z"></y><z class="b"></z>`);
60
+ params.d = true;
61
+ is(el.outerHTML, `<x class="x"></x><y class="y z"></y><z class="b c"></z>`);
62
+ c.value = 'w'
63
+ is(el.outerHTML, `<x class="x"></x><y class="y w"></y><z class="b c"></z>`);
64
+ })
65
+
66
+ test('spread props: base', async () => {
67
+ let el = h`<input :id="0" :="{for:1, title:2, help:3, type:4, placeholder: 5, value: 6}" :value="7"/>`
68
+ let params = sprae(el)
69
+ is(el.outerHTML, `<input id="0" for="1" title="2" help="3" type="4" placeholder="5" value="7">`)
70
+ })
71
+
72
+ test('data: base', async () => {
73
+ let el = h`<input :data="{a:1, fooBar:2}"/>`
74
+ let params = sprae(el)
75
+ is(el.outerHTML, `<input data-a="1" data-foo-bar="2">`)
76
+ })
77
+
78
+ test('aria: base', async () => {
79
+ let el = h`<input type="text" id="jokes" role="combobox" :aria="{controls:'joketypes', autocomplete:'list', expanded:false, activeOption:'item1', activedescendant:'', xxx:null}"/>`
80
+ sprae(el)
81
+ 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="">`)
82
+ })
83
+
84
+ test('value: direct', async () => {
85
+ let el = h`<input :value="a" />`
86
+ let state = sprae(el, {a:1})
87
+ is(el.value, '1')
88
+ is(el.outerHTML, `<input value="1">`)
89
+ state.a = 2
90
+ is(el.value, '2')
91
+ is(el.outerHTML, `<input value="2">`)
92
+
93
+ el.value = 3
94
+ // el.dispatchEvent(new window.Event('change'))
95
+ // is(state.a, '3')
23
96
  })
24
97
 
25
98
  test('text: core', async () => {
26
99
  let el = h`<div :text="text"></div>`
27
100
  let params = sprae(el, {text:'abc'})
28
- is(el.outerHTML, `<div class="∴text">abc</div>`)
101
+ is(el.outerHTML, `<div>abc</div>`)
29
102
  params.text = null
30
- is(el.outerHTML, `<div class="∴text"></div>`)
103
+ is(el.outerHTML, `<div></div>`)
31
104
  })
32
105
 
33
106
  test('conditions: base', async () => {
34
107
  let el = h`<p>
35
108
  <span :if="a==1">a</span>
36
- <span :else-if="a==2">b</span>
109
+ <span :else :if="a==2">b</span>
37
110
  <span :else >c</span>
38
111
  </p>`
39
112
 
40
113
  const params = sprae(el, { a: 1 })
41
114
 
42
- is(el.innerHTML, '<span class="∴if">a</span>')
115
+ is(el.innerHTML, '<span>a</span>')
43
116
  params.a = 2
44
- is(el.innerHTML, '<span class="∴else-if">b</span>')
117
+ is(el.innerHTML, '<span>b</span>')
45
118
  params.a = 3
46
- is(el.innerHTML, '<span class="∴else">c</span>')
47
-
48
- delete params.a
119
+ is(el.innerHTML, '<span>c</span>')
120
+ params.a = null
121
+ is(el.innerHTML, '<span>c</span>')
49
122
  })
50
123
 
51
124
  test('conditions: short with insertions', async () => {
52
125
  let el = h`<p>
53
126
  <span :if="a==1" :text="'1:'+a"></span>
54
- <span :else-if="a==2" :text="'2:'+a"></span>
127
+ <span :else :if="a==2" :text="'2:'+a"></span>
55
128
  <span :else :text="a"></span>
56
129
  </p>`
57
130
 
58
131
  const params = sprae(el, { a: 1 })
59
132
 
60
- is(el.innerHTML, '<span class="∴text ∴if">1:1</span>')
133
+ is(el.innerHTML, '<span>1:1</span>')
61
134
  params.a = 2
62
- is(el.innerHTML, '<span class="∴text ∴else-if">2:2</span>')
135
+ is(el.innerHTML, '<span>2:2</span>')
63
136
  params.a = 3
64
- is(el.innerHTML, '<span class="∴text ∴else">3</span>')
137
+ is(el.innerHTML, '<span>3</span>')
65
138
  params.a = 4
66
- is(el.innerHTML, '<span class="∴text ∴else">4</span>')
139
+ is(el.innerHTML, '<span>4</span>')
67
140
 
68
141
  params.a = 1
69
- is(el.innerHTML, '<span class="∴text ∴if">1:1</span>')
142
+ is(el.innerHTML, '<span>1:1</span>')
70
143
  params.a = 4
71
- is(el.innerHTML, '<span class="∴text ∴else">4</span>')
144
+ is(el.innerHTML, '<span>4</span>')
72
145
 
73
- delete params.a
146
+ params.a = null
74
147
  })
75
148
 
76
149
  test('conditions: reactive values', async () => {
77
150
  let el = h`<p>
78
151
  <span :if="a==1" :text="'1:'+a"></span>
79
- <span :else-if="a==2" :text="'2:'+a"></span>
152
+ <span :else :if="a==2" :text="'2:'+a"></span>
80
153
  <span :else :text="a"></span>
81
154
  </p>`
82
155
 
83
156
  const a = signal(1)
84
157
  sprae(el, { a })
85
158
 
86
- is(el.innerHTML, '<span class="∴text ∴if">1:1</span>')
159
+ is(el.innerHTML, '<span>1:1</span>')
87
160
  a.value = 2
88
- is(el.innerHTML, '<span class="∴text ∴else-if">2:2</span>')
161
+ is(el.innerHTML, '<span>2:2</span>')
89
162
  a.value = 3
90
- is(el.innerHTML, '<span class="∴text ∴else">3</span>')
163
+ is(el.innerHTML, '<span>3</span>')
91
164
  a.value = 4
92
- is(el.innerHTML, '<span class="∴text ∴else">4</span>')
165
+ is(el.innerHTML, '<span>4</span>')
93
166
 
94
167
  a.value = 1
95
- is(el.innerHTML, '<span class="∴text ∴if">1:1</span>')
168
+ is(el.innerHTML, '<span>1:1</span>')
96
169
  a.value = 4
97
- is(el.innerHTML, '<span class="∴text ∴else">4</span>')
170
+ is(el.innerHTML, '<span>4</span>')
98
171
  })
99
172
 
100
173
 
@@ -107,12 +180,34 @@ test('each: base', async () => {
107
180
  const params = sprae(el, { b: [] })
108
181
 
109
182
  is(el.innerHTML, '')
183
+ console.log('set 1,2')
110
184
  params.b = [1,2]
111
- is(el.innerHTML, '<span class="∴each ∴text">1</span><span class="∴each ∴text">2</span>')
185
+ is(el.innerHTML, '<span>1</span><span>2</span>')
112
186
  params.b = []
113
187
  is(el.innerHTML, '')
114
- delete params.b
188
+ params.b = null
189
+ is(el.innerHTML, '')
190
+ })
191
+
192
+ test('each: loop within loop', async () => {
193
+ let el = h`<p>
194
+ <x :each="b in c"><y :each="a in b" :text="a"></y></x>
195
+ </p>`
196
+
197
+ const params = sprae(el, { c: [[1,2], [3,4]] })
198
+
199
+ is(el.innerHTML, '<x><y>1</y><y>2</y></x><x><y>3</y><y>4</y></x>')
200
+ // console.log('set 1,2')
201
+ params.c = [[5,6], [3,4]]
202
+ is(el.innerHTML, '<x><y>5</y><y>6</y></x><x><y>3</y><y>4</y></x>')
203
+ // params.c[1] = [7,8]
204
+ params.c = [params.c[0], [7,8]]
205
+ is(el.innerHTML, '<x><y>5</y><y>6</y></x><x><y>7</y><y>8</y></x>')
206
+ // is(el.innerHTML, '<span>1</span><span>2</span>')
207
+ params.c = []
115
208
  is(el.innerHTML, '')
209
+ // params.b = null
210
+ // is(el.innerHTML, '')
116
211
  })
117
212
 
118
213
  test('each: reactive values', async () => {
@@ -125,10 +220,10 @@ test('each: reactive values', async () => {
125
220
 
126
221
  is(el.innerHTML, '')
127
222
  b.value = [1,2]
128
- is(el.innerHTML, '<span class="∴each ∴text">1</span><span class="∴each ∴text">2</span>')
223
+ is(el.innerHTML, '<span>1</span><span>2</span>')
129
224
  b.value = []
130
225
  is(el.innerHTML, '')
131
- delete params.b
226
+ params.b = null
132
227
  is(el.innerHTML, '')
133
228
  })
134
229
 
@@ -137,28 +232,48 @@ test('each: loop with condition', async () => {
137
232
  <span :each="a in b" :text="a" :if="c"></span>
138
233
  </p>`
139
234
 
140
- const params = sprae(el, { b: [1,2], c:false })
235
+ const params = sprae(el, { b: [1,2], c: false })
141
236
 
142
237
  is(el.innerHTML, '')
143
238
  params.c = true
144
- is(el.innerHTML, '<span class="∴each ∴text ∴if">1</span><span class="∴each ∴text ∴if">2</span>')
239
+ is(el.innerHTML, '<span>1</span><span>2</span>')
240
+ params.b = [1]
241
+ is(el.innerHTML, '<span>1</span>')
242
+ params.b = null
243
+ is(el.innerHTML, '')
244
+ })
245
+
246
+ test('each: condition with loop', async () => {
247
+ let el = h`<p>
248
+ <span :if="c" :each="a in b" :text="a"></span>
249
+ <span :else :text="c"></span>
250
+ </p>`
251
+
252
+ const params = sprae(el, { b: [1,2], c: false })
253
+
254
+ is(el.innerHTML, '<span>false</span>')
255
+ params.c = true
256
+ is(el.innerHTML, '<span>1</span><span>2</span>')
145
257
  params.b = [1]
146
- is(el.innerHTML, '<span class="∴each ∴text ∴if">1</span>')
147
- delete params.b
258
+ is(el.innerHTML, '<span>1</span>')
259
+ params.b = null
148
260
  is(el.innerHTML, '')
261
+ console.log('c=false')
262
+ params.c = false
263
+ is(el.innerHTML, '<span>false</span>')
149
264
  })
150
265
 
151
266
  test('each: loop within condition', async () => {
152
267
  let el = h`<p>
153
268
  <x :if="a==1"><y :each="i in a" :text="i"></y></x>
154
- <x :else-if="a==2"><y :each="i in a" :text="-i"></y></x>
269
+ <x :else :if="a==2"><y :each="i in a" :text="-i"></y></x>
155
270
  </p>`
156
271
 
157
272
  const params = sprae(el, { a: 1 })
158
273
 
159
- is(el.innerHTML, '<x class="∴if"><y class="∴each ∴text">1</y></x>')
274
+ is(el.innerHTML, '<x><y>1</y></x>')
160
275
  params.a = 2
161
- is(el.innerHTML, '<x class="∴else-if"><y class="∴each ∴text">-1</y><y class="∴each ∴text">-2</y></x>')
276
+ is(el.innerHTML, '<x><y>-1</y><y>-2</y></x>')
162
277
  params.a = 0
163
278
  is(el.innerHTML, '')
164
279
  })
@@ -167,17 +282,99 @@ test('each: condition within loop', async () => {
167
282
  let el = h`<p>
168
283
  <x :each="a in b">
169
284
  <y :if="a==1" :text="'1:'+a"></y>
170
- <y :else-if="a==2" :text="'2:'+a"></y>
285
+ <y :else :if="a==2" :text="'2:'+a"></y>
171
286
  <y :else :text="a"></y>
172
287
  </x>
173
288
  </p>`
174
289
 
175
290
  const params = sprae(el, { b: [1,2,3] })
176
291
 
177
- is(el.innerHTML, '<x class="∴each"><y class="∴text ∴if">1:1</y></x><x class="∴each"><y class="∴text ∴else-if">2:2</y></x><x class="∴each"><y class="∴text ∴else">3</y></x>')
292
+ is(el.innerHTML, '<x><y>1:1</y></x><x><y>2:2</y></x><x><y>3</y></x>')
178
293
  params.b = [2]
179
- is(el.innerHTML, '<x class="∴each"><y class="∴text ∴else-if">2:2</y></x>')
180
- delete params.b
294
+ is(el.innerHTML, '<x><y>2:2</y></x>')
295
+ params.b = null
181
296
  is(el.innerHTML, '')
182
297
  })
183
298
 
299
+ test('on: base', () => {
300
+ let el = h`<div :on="{click(e){log.push('click')},x}"></div>`
301
+ let log = signal([])
302
+ let params = sprae(el, {x(){log.value.push('x')}, log})
303
+
304
+ is(el.outerHTML, `<div></div>`);
305
+ el.dispatchEvent(new window.Event('click'));
306
+ is(log.value, ['click'])
307
+ el.dispatchEvent(new window.Event('x'));
308
+ is(log.value, ['click','x'])
309
+
310
+ params.x = function(){log.value.push('xx')}
311
+ el.dispatchEvent(new window.Event('x'));
312
+ is(log.value, ['click','x','xx']);
313
+
314
+ params.x = null;
315
+ el.dispatchEvent(new window.Event('x'));
316
+ is(log.value, ['click','x','xx']);
317
+ })
318
+
319
+ test('with: inline', () => {
320
+ let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
321
+ let state = sprae(el, {baz: 'qux'})
322
+ // FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
323
+ is(el.innerHTML, `<y>barqux</y>`)
324
+ state.baz = 'quux'
325
+ is(el.innerHTML, `<y>barquux</y>`)
326
+ })
327
+ test('with: inline reactive', () => {
328
+ let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
329
+ let baz = signal('qux')
330
+ let state = sprae(el, {baz})
331
+ // FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
332
+ is(el.innerHTML, `<y>barqux</y>`)
333
+ baz.value = 'quux'
334
+ is(el.innerHTML, `<y>barquux</y>`)
335
+ })
336
+ test('with: data', () => {
337
+ let el = h`<x :with="x"><y :text="foo"></y></x>`
338
+ let state = sprae(el, {x:{foo:'bar'}})
339
+ is(el.innerHTML, `<y>bar</y>`)
340
+ console.log('update')
341
+ Object.assign(state, {x:{foo:'baz'}})
342
+ is(el.innerHTML, `<y>baz</y>`)
343
+ })
344
+ test('with: transparency', () => {
345
+ // NOTE: y:text initializes through directive, not through parent
346
+ // therefore by default :text uses parent's state, not defined by element itself
347
+ let el = h`<x :with="{foo:'foo'}"><y :with="b" :text="foo+bar"></y></x>`
348
+ let params = sprae(el, {b:{bar:'bar'}})
349
+ is(el.innerHTML, `<y>foobar</y>`)
350
+ params.b.bar = 'baz'
351
+ is(el.innerHTML, `<y>foobaz</y>`)
352
+ })
353
+ test('with: reactive transparency', () => {
354
+ let el = h`<x :with="{foo:1}"><y :with="b.c" :text="foo+bar"></y></x>`
355
+ const bar = signal('2')
356
+ sprae(el, {b:{c:{bar}}})
357
+ is(el.innerHTML, `<y>12</y>`)
358
+ bar.value = '3'
359
+ is(el.innerHTML, `<y>13</y>`)
360
+ })
361
+ test('with: writes to state', () => {
362
+ let a = h`<x :with="{a:1}"><y :on="{x(){a++}}" :text="a"></y></x>`
363
+ sprae(a)
364
+ is(a.innerHTML, `<y>1</y>`)
365
+ a.firstChild.dispatchEvent(new window.Event('x'))
366
+ is(a.innerHTML, `<y>2</y>`)
367
+ a.firstChild.dispatchEvent(new window.Event('x'))
368
+ is(a.innerHTML, `<y>3</y>`)
369
+ })
370
+
371
+ test('reactives', async () => {
372
+ let a = new Promise((ok) => setTimeout(() => ok(2), 10))
373
+
374
+ let el = h`<x :text="a">1</x>`
375
+ sprae(el, {a})
376
+ is(el.outerHTML, `<x></x>`)
377
+
378
+ await time(20)
379
+ is(el.outerHTML, `<x>2</x>`)
380
+ })
@@ -1,14 +0,0 @@
1
- import { directive, parseExpr } from '../core.js'
2
-
3
- // hidden attribute directive example
4
- directive('hidden', (el) => {
5
- let expr = el.getAttribute(':hidden');
6
- el.removeAttribute(':hidden');
7
- let evaluate = parseExpr(expr);
8
- return (state) => {
9
- let value = evaluate(state);
10
- if (value || value === '') el.setAttribute('hidden', '')
11
- else el.removeAttribute('hidden')
12
- }
13
- })
14
-
@@ -1,34 +0,0 @@
1
- import { directive, parseExpr } from '../core.js'
2
-
3
- directive('if', (el) => {
4
- let expr = el.getAttribute(':if');
5
- el.removeAttribute(':if');
6
-
7
- let cur = el, els = [el], clauses = [parseExpr(expr)], holder = new Text
8
-
9
- // collect clauses
10
- while (cur = el.nextElementSibling) {
11
- if (expr = cur.getAttribute(':else-if')) {
12
- cur.removeAttribute(':else-if');
13
- cur.classList.add('∴else-if')
14
- cur.remove();
15
- els.push(cur); clauses.push(parseExpr(expr));
16
- continue
17
- }
18
- if (cur.hasAttribute(':else')) {
19
- cur.removeAttribute(':else');
20
- cur.classList.add('∴else')
21
- cur.remove();
22
- els.push(cur); clauses.push(() => 1);
23
- }
24
- break;
25
- }
26
-
27
- cur = els[0]
28
-
29
- return state => {
30
- let idx = clauses.findIndex(match => match(state));
31
- if (idx >= 0) cur.replaceWith(cur = els[idx]);
32
- else cur.replaceWith(cur = holder);
33
- }
34
- })
@@ -1,5 +0,0 @@
1
- // order defines precedence
2
- import './each.js'
3
- import './hidden.js'
4
- import './text.js'
5
- import './if.js' // if must go last, after other directives are initialized, since it removes :else, :else-if from tree
@@ -1,13 +0,0 @@
1
- import { directive, parseExpr } from '../core.js'
2
-
3
- // hidden attribute directive example
4
- directive('text', (el) => {
5
- let expr = el.getAttribute(':text');
6
- el.removeAttribute(':text');
7
- let evaluate = parseExpr(expr);
8
- return (state) => {
9
- let value = evaluate(state);
10
- el.textContent = value == null ? '' : value;
11
- }
12
- })
13
-